package org.broadinstitute.hellbender.tools.spark.sv.discovery.alignment;

import com.google.common.collect.Lists;
import htsjdk.samtools.TextCigarCodec;
import org.broadinstitute.hellbender.GATKBaseTest;
import org.broadinstitute.hellbender.tools.spark.sv.discovery.TestUtilsForAssemblyBasedSVDiscovery;
import org.broadinstitute.hellbender.utils.SimpleInterval;
import org.broadinstitute.hellbender.utils.read.CigarUtils;
import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import scala.Tuple2;

import java.util.*;
import java.util.stream.Collectors;

import static org.broadinstitute.hellbender.tools.spark.sv.StructuralVariationDiscoveryArgumentCollection.DiscoverVariantsFromContigAlignmentsArgumentCollection.GAPPED_ALIGNMENT_BREAK_DEFAULT_SENSITIVITY;
import static org.broadinstitute.hellbender.tools.spark.sv.discovery.TestUtilsForAssemblyBasedSVDiscovery.*;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;

public class AssemblyContigAlignmentsRDDProcessorUnitTest extends GATKBaseTest {

    // step 1: parse and primitive filter ==============================================================================

    @DataProvider
    private Object[][] forTestingNotDiscardForBadMQ() {

        final List<Object[]> data = new ArrayList<>(20);

        final AlignedContig outForEmptyAlignments = new AlignedContig("unmapped", "AAAAAAAAAA".getBytes(), Collections.emptyList());
        data.add(new Object[]{outForEmptyAlignments, false});

        final AlignedContig outForSingleBadMapping = new AlignedContig("badMap", makeDummySequence(151, (byte)'T'),
                Collections.singletonList(new AlignmentInterval(new SimpleInterval("chr1", 1000000, 1000150),
                        1, 151, TextCigarCodec.decode("151M"), true, 5, 39, 100, ContigAlignmentsModifier.AlnModType.NONE)));
        data.add(new Object[]{outForSingleBadMapping, false});

        final AlignmentInterval intervalOne = new AlignmentInterval(
                new SimpleInterval("chr21", 100001, 100100),
                1, 100, TextCigarCodec.decode("100M220S"),
                true, 60, 0, 100, ContigAlignmentsModifier.AlnModType.NONE);
        final AlignmentInterval intervalTwo = new AlignmentInterval(
                new SimpleInterval("chr21", 100099, 100122),
                99, 122, TextCigarCodec.decode("98S24M78S"),
                true, 10, 3, 241, ContigAlignmentsModifier.AlnModType.NONE);
        final AlignedContig outForOnlyOneGaplessGoodMapping = new AlignedContig("lonelyGood", makeDummySequence(320, (byte)'C'),
                Arrays.asList(intervalOne, intervalTwo));
        data.add(new Object[]{outForOnlyOneGaplessGoodMapping, false});

        final AlignedContig stayForOneGappedGoodMapping =
                fromPrimarySAMRecordString("asm000576:tig00001\t16\tchr1\t30894493\t60\t106S126M67D873M\t*\t0\t0\tGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGTGAGTATGAGTGGGTGAGAGTGTGAGTGGGTGAGAGTGTGTGTGGATGAGTGTGTGTGGGTGAGAGTGTGTGGGTGTATGTGTGGGCTACGTGTGTGTGGGTGTGTGTGGATGAGTGTGAGTGTGGGTGAGAGTGTGTGGGTGTGTGTGGGGAGTGTGTGTGTGTATGTGTGAATGTGTGGGCGTACACAGGCATGCACATGCACACATCCAAGCACATTCAGGGGTATTTCTGGGCGTGTGTGTCTGTCTAGGTGCATCCATGTGTGTATGCCTAGGGCTGTCTCGAGTGGCTTAGATGCCTGTGTGGGTCTGAGTGTGTCCATGTTAGCGTGGAGGGCTGTGTCTCTGAGTGGTACCTGTGCACATGGCAGTGTATGTGTGTCCCTGTCTCTGTCTCTACACGTGTGCACAGGACAAGGGTATTTGCACAGGTCCATGTTCCTCCATTTGTAAGCATGCATCTGAGCCTCCCAAGGTAGAGAGCATCTCCCAACTTCCTTAATCCACAGTGTTGGGGGTAAGCCCCTGGAGCTGAGGCAGGGGGAGGGACAGCAGATGGGGGACTCCTGATCCCCCTGGCATAGGCTACAAACACTTTTTCCTACAAAGACACCAGGGCCCCATGCATCAGCACAGACACACAACAACATGCCATCACACATGTTGCAGGTACACGCAAACCCCTAACCCAAACTAACGTCCTAAACAGAAACATGGCCTTAGCTCCACGTGGAAAAAGGATTACCTGGCCTCACTTGCAGCCTTGCAGACCATCCCTCAAGACACAGCTGAGCTCCCTCTGGATCTGAGATTGTCCCCACAAGCCCCCCAGCTCCGCAGCTCACATGAGAATAGGATCCAGCCAGCCAGGGCTCAGCTCCCAGAGGCCATGTAGGGGCTTCAGCCCCGGGGAAGCTGTTCCACTTCTCACTGCTGAGTGACCCTCAACAGCCACACTTCATCTCTGGGTTCAGTCTCCTCATCTGAACAG\t*\tSA:Z:chr2,32916450,-,107M998S,0,2;\tMD:Z:46C79^GTGTGGGTGAGAGTGTGTGGGTGTGTGTGGGGTGTGTGTGGGTGAGTGTGTGTGGATGACTGTGAGT396C40T435\tRG:Z:GATKSVContigAlignments\tNM:i:70\tAS:i:901\tXS:i:0",
                        true);
        data.add(new Object[]{stayForOneGappedGoodMapping, true});

        final AlignedContig stayForOneGoodMapping =
                fromPrimarySAMRecordString("asm022620:tig00006\t0\tchr1\t176967755\t60\t1022M\t*\t0\t0\tCTGTTGCTAAGTAGTTTAGGTCAAGGAGACCTCCTGCTCTTGAGGGGCCTGAAAATTAAGAAAATCAACTCAAATTGGAAGGATGTAGAGGAGTGCAGCGTAGACCCCACCCTGATCACATTTAGAGGCAGGAGTGCAGTGTAGACCCCACCCTGATCACATTTAGAGGCAGGAGTGTAGTGTAGACCCCATCCTGATCACATTTAGAGGCAGGAGTGTAGTGTAGACCCCATCCTGATCACATTTAAAGGCAGCAGGGCACAGTGGTTTCAGGACATGGATTCTGGCGTTAGCCTGCCTATTTCAAATGCCAGCTTAGCCACCAATCACTGTGTGACTTTGGGCAAGTTCCTTCATCATTCTGTAACCCCATATCCTCATCTGTAAAATGAGGCAGATGGTAATAACTGTATTTATTTGACAAAGTTGTTATGAGGAGGAACTAGCTAATGTAATTAAAGTGCCTAGAACAGTGCGTGGCATTTAGTGAGTGCCACACATAGAATCATTTGTTAAATAAGTTAAATTGATCAATTACATGCATCAAATATAAATACTATAAGGAGTGTTTCCCAAATTTCAATAGAGGAAGATGTGTCTTGCCCTAAGGTGCAAATGCAATGCCAACGCCAGGCTTTAACTCAGATTATAGATGTCTTCACATAGGCTGGAGAGGAGGCAACATGAGTATGGAAAGGCCAAAGCCCCGACACCCCACCTCCCACCTCCCACTTCCCACCCAGTGGTCACTTCACTGGGCAACTCACTCTACAGCATTGACATTAAACACAAAGAGCAGGGAAGTGCATATTGTGAAATGGCAAGAGAAGTCAGCGAGTAGCAAAATTGCACCAGCAAAGGAATAAAGGGGAATTTACCTAATTTCTTCCCTCTAGTCCGGAATGATCTGTCAGAGAGATGAGAAATCTTACTTGATTTATTTAGCAAATATTTGTGGAGGCCTATTGTGTGTGAACTGTGATATTAGGCAACAGGAATTGGGATAAAGAGATTCCTGCCCT\t*\tMD:Z:287A734\tRG:Z:GATKSVContigAlignments\tNM:i:1\tAS:i:1017\tXS:i:165",
                        false);
        data.add(new Object[]{stayForOneGoodMapping, true});

        final AlignedContig stayForTwoGoodMappings =
                fromPrimarySAMRecordString("asm017085:tig00001\t0\tchr10\t91874806\t60\t142S150M\t*\t0\t0\tAAAAAAAAGAATCTCTGAGAAAATACACAGGAAACTGATAATCTGGTTGCTTCTATGAAGGAAACGGCATGGCAGGGGATGGGATGGAAGGGAGGCTTGCTTTTCACTCTATTGCCTTTTGAATTCTGTACTGTGTGCATTTCACAATGTTGTGCTTAGAATTATAGAAAAACAAAAGCACAGCTCTGAACATGGGTGTCCCAATGGCACCTCAGCCTTAACCAACTTCTGCCTCTATTGCAAGCTCTTAATTGGCCTCACCTCTCACTTTCCTCATACAATTGACATTCCA\t*\tSA:Z:chr10,91873437,+,145M147S,60,0;\tMD:Z:150\tRG:Z:GATKSVContigAlignments\tNM:i:0\tAS:i:150\tXS:i:19",
                        true);
        data.add(new Object[]{stayForTwoGoodMappings, true});

        return data.toArray(new Object[data.size()][]);
    }
    @Test(dataProvider = "forTestingNotDiscardForBadMQ", groups = "sv")
    public void testNotDiscardForBadMQ(final AlignedContig contig, final boolean shouldKeep) {
        Assert.assertEquals(contig.hasGoodMQ(), shouldKeep);
    }

    // step 2: score possible configurations and pick the best ones ====================================================

    @Test(groups = "sv")
    public void testHeuristicSpeedUpWhenFacingManyMappings() {
        final AlignedContig alignedContig = fromPrimarySAMRecordString("asm010147:tig00010\t16\tchr6\t31427489\t43\t4S54M2I26M7D22M4I33M4I70M2I109M2I103M1525S\t*\t0\t0\tATTCTATATATATATTCTATATATATATTCTATATATATATTCTATATATATATTCTATGTATGTATTCTATATATATATTTTATATATTTATTCTATGTATATATTCTACATATATATTCTATATATATATTCGATATATGTTCGATATATATATTTGATATATATATTCTATATATATATTCTATATATATATTCTATATATATTCTATATATATTCTATATATATATATTCTATATATATTCTATATATATATTCTATATATATATTCTATATATATTCTATATATATTCTATATATATATTCTATATATATTCCATATATATATTCTATATATATATTCCATATATATTCCATATATATATTCTATATATATTCCATATATATATTCCACATATATATTCCATATATATATTCCACACATATATTCTATATATATATTCCATACATATATTCTATATATATATATTCCATACATATATTCTATATATATATTCCATACATATATTCTATATATATATATTCCATACATATATTCTATATATATATATTCCATACATATATTCTATATATATATATTCCATACATATATTCTATATATGTATATTCCATACATATATTCCATATATATATTCCATACATATATTCCATATATGTATATTCCATATATATATTCCATATATATATATTCCATATATATATTCCATATATATATTCCATATATATGTTCCATATATATATTCCATATATATGTTCCATATATATATTCTATATATATGTAACATATATATATTCCATATATATGGAACATATATATATTCTATATATATGGAACATATATATATTCTATATATATGGAACATATATATATTCTATATATCTGTTCCATATATATATTCTATATATCTGTTCCATATATATATTCTATATATCTGTTCCATATATATGTTCTATATATCTGTTCCATATATATATTCTATATATCTGTTCCATATATATATTCTATATATCTGTTCCATATATATATTCTATATATATATTCTATACATTCTATATATAATATATATATTCTATATATTCTATATATAATATATATATTCTATGTATATATTCTATATATAATATATATATTCTATGTATATATTCTATATATATTCTATATATATTCTATGTATATATTCTATATATATTCTATATATATTCCATATATATTCTATATATATTCTATGTATATTCGATATATATATTCTATATATATTCTATATATTCGATATATATATTCTATATATATTCTATATATTCGATACATATATTCTATATATAATCTATATATTATATATATTCTATATATATATCCTATATATATTCTATATATTCTATATATATCCCATATATATTCTATATATTCTATATATACATCCCATATATATCCTGTATATCCTATATATATATCCTATATATATCCTGTATATCCTATATATATATCCTATATATATATCCTGTATATCCTATATATATATATCCTATATATATCCTATATATCCTATATATATCCTATATATGTATCCTATATATACATCCTTTATATCCTATACATATATCCTATATGTCCTATACATATATCCTATATATATCCTATATATCCTATATATATATCCTATATATATATTCGATATATATCCTATATATCCTATATATATATCCCATATATATCCTATATATCCCATATATATCCTATATATCCTATATATCCTATATATATATCCTATATATATCCTATATATATCCTATATATATATCCTGTATATATATCCTATATATATATATCCTGTATATATATCCTATATATATATCCTGTATATATATCCTATATATATATATCCTGTATATATATCCTATATATATATATCCTGTATATATATCCTATATATATATATCCTGTATATATATCCTATATATA\t*\tSA:Z:chr6,31428609,-,1753H65M9D61M4I77M,17,22,113;chr6,31428242,-,883H188M4D95M794H,43,36,103;chr3,152694227,+,1245H26M2I118M569H,17,9,91;chr7,15531019,-,1172H147M7I62M2D28M544H,19,31,86;chr6,31428246,-,679H125M1156H,31,9,80;chr4,12386256,-,780H100M1080H,0,7,65;chrX,50228609,+,739H24M2D22M4I49M1122H,16,7,52;chr3,151030799,+,150H24M2D35M2D57M1694H,43,10,50;chr3,151030872,+,399H47M1514H,10,0,47;chr6,31428687,-,1467H40M453H,43,1,35;chr6_GL000253v2_alt,2736084,-,215M4I26M4I142M1569H,7,38,193;chr6_GL000256v2_alt,2730310,-,1017H129M13D57M15D57M2D38M9I134M2I58M459H,30,76,177;chrUn_JTFH01001202v1_decoy,266,-,306H38M6D23M6D89M4I24M1D139M2D64M1273H,34,45,148;chrUn_JTFH01001628v1_decoy,267,+,918H273M4I24M2I33M4D28M2D55M623H,60,52,137;\tMD:Z:57A17C4^TCTATTC3A8A3C21C5T18C0T34T0C1A92A0T1C22T23T10T23T12T1T10T12T1T1T13\tRG:Z:GATKSVContigAlignments\tNM:i:46\tAS:i:175\tXS:i:138",
                true);

        final AlignedContig.GoodAndBadMappings goodAndBadMappings =
                alignedContig.heuristicSpeedUpWhenFacingManyMappings(hg38CanonicalChromosomes, 175);

        final List<AlignmentInterval> alignments = alignedContig.getAlignments();
        final List<AlignmentInterval> expectedBad = Arrays.asList(new AlignmentInterval("chr3,151030872,+,399H47M1514H,10,0,47"),
                new AlignmentInterval("chr4,12386256,-,780H100M1080H,0,7,65"));
        alignments.removeAll(expectedBad);
        Assert.assertEquals(goodAndBadMappings.getGoodMappings(), alignments);
        Assert.assertEquals(goodAndBadMappings.getBadMappings(), expectedBad);
    }

    @DataProvider
    private Object[][] forGetBetterNonCanonicalMapping() {
        final List<Object[]> data = new ArrayList<>(20);

        // note chromosome names is hacked to use test seq dict

        final AlignmentInterval altOne = fromSAMRecordString("asm000099:tig00029\t2064\tchr1_KI270762v1_alt\t207249\t2\t38H252M229H\t*\t0\t0\tGAGCATCTGACAGCCTGGAACAGCACCCATACGCCCAGATGAGCATCTGACAGCCTGAAACAGCACCCTGCACCCCCAGGTGTGCAACTGACAGCCTGGAACAGCACACACTCACCCAGGCCAGCATCTGATGGCCTGGAACGGCACCCACACCCCCAGGTAAGCATCCGACATCCTGAAACAGCTCCCACACCCCCAGGCGAGCATCTGACAGCCTGGAGCAGTGCCCACACCCCCAGGTGAGCATCTGAC\t*\tSA:Z:chr1,2655749,-,125S394M,1,42;chr1,2667827,-,66M453S,0,3;chr1,2761512,-,95S50M374S,0,3;chrUn_JTFH01001111v1_decoy,1,+,22S273M224S,46,12;chr1_KI270762v1_alt,207251,-,66M453S,0,2;\tMD:Z:57G11A2T13C20C12G0G19T19G30A7T7C15C0A4T21\tRG:Z:GATKSVContigAlignments\tNM:i:15\tAS:i:177\tXS:i:172", true);
        final AlignmentInterval altTwo = fromSAMRecordString("asm000099:tig00029\t2064\tchr1_KI270762v1_alt\t207251\t0\t66M453H\t*\t0\t0\tGCATCTGACAGCCTGCAACCGCACCCATACGCCCAGATGAGCATCTGACAGCCTGGAACAGCACCC\t*\tSA:Z:chr1,2655749,-,125S394M,1,42;chr1,2667827,-,66M453S,0,3;chr1,2761512,-,95S50M374S,0,3;chrUn_JTFH01001111v1_decoy,1,+,22S273M224S,46,12;chr1_KI270762v1_alt,207249,-,38S252M229S,2,15;\tMD:Z:15G3A46\tRG:Z:GATKSVContigAlignments\tNM:i:2\tAS:i:56\tXS:i:56", true);
        final AlignmentInterval canonicalOne = fromSAMRecordString("asm000099:tig00029\t16\tchr21\t2655749\t1\t125S394M\t*\t0\t0\tGCATCTGACAGCCTGCAACCGCACCCATACGCCCAGATGAGCATCTGACAGCCTGGAACAGCACCCATACGCCCAGATGAGCATCTGACAGCCTGAAACAGCACCCTGCACCCCCAGGTGTGCAACTGACAGCCTGGAACAGCACACACTCACCCAGGCCAGCATCTGATGGCCTGGAACGGCACCCACACCCCCAGGTAAGCATCCGACATCCTGAAACAGCTCCCACACCCCCAGGCGAGCATCTGACAGCCTGGAGCAGTGCCCACACCCCCAGGTGAGCATCTGACATCGTGGAGCAGCACCCACAGCCCAAGGTGAGCATCTGACAACCAGGAGCAGCACCCACACACCCAGGCGAGCATCAGAATGCACGGAGCATCACCCACACCCCCAGGCGAGCATCCGACAGCCTGGAGCAGCACCCACACCCCCAGGCGAGCATCTGACAGCCTGGAGCAGTGCCCACACCCCCAGGTGAGCATCTGACAGCGTGGAGCAGCACCCACAGCCCAAGGT\t*\tSA:Z:chr1,2667827,-,66M453S,0,3;chr1,2761512,-,95S50M374S,0,3;chrUn_JTFH01001111v1_decoy,1,+,22S273M224S,46,12;chr1_KI270762v1_alt,207249,-,38S252M229S,2,15;chr1_KI270762v1_alt,207251,-,66M453S,0,2;\tMD:Z:15G4C3A8T0G12T5G1A9A2A5G1A4G2G1G0T3G1G4G44A22G59C14T3C10G10T2C42T1C8T0G1T9C0A7A1A0G14G3C4A3A11C4\tRG:Z:GATKSVContigAlignments\tNM:i:42\tAS:i:184\tXS:i:177", true);
        final AlignmentInterval canonicalTwo = fromSAMRecordString("asm000099:tig00029\t2064\tchr21\t2667827\t0\t66M453H\t*\t0\t0\tGCATCTGACAGCCTGCAACCGCACCCATACGCCCAGATGAGCATCTGACAGCCTGGAACAGCACCC\t*\tSA:Z:chr1,2655749,-,125S394M,1,42;chr1,2761512,-,95S50M374S,0,3;chrUn_JTFH01001111v1_decoy,1,+,22S273M224S,46,12;chr1_KI270762v1_alt,207249,-,38S252M229S,2,15;chr1_KI270762v1_alt,207251,-,66M453S,0,2;\tMD:Z:10A4G3A46\tRG:Z:GATKSVContigAlignments\tNM:i:3\tAS:i:51\tXS:i:51", true);
        final AlignmentInterval canonicalThree = fromSAMRecordString("asm000099:tig00029\t2064\tchr21\t2761512\t0\t95H50M374H\t*\t0\t0\tAAACAGCACCCTGCACCCCCAGGTGTGCAACTGACAGCCTGGAACAGCAC\t*\tSA:Z:chr1,2655749,-,125S394M,1,42;chr1,2667827,-,66M453S,0,3;chrUn_JTFH01001111v1_decoy,1,+,22S273M224S,46,12;chr1_KI270762v1_alt,207249,-,38S252M229S,2,15;chr1_KI270762v1_alt,207251,-,66M453S,0,2;\tMD:Z:25A3T11A8\tRG:Z:GATKSVContigAlignments\tNM:i:3\tAS:i:35\tXS:i:35", true);

        data.add(new Object[]{Arrays.asList(canonicalOne, canonicalTwo, canonicalThree, altOne, altTwo), 184, null});

        final AlignmentInterval canonical = fromSAMRecordString("asm002362:tig00002\t16\tchr21\t1422222\t60\t75M56I139M\t*\t0\t0\tATGCTGGGGAATTTGTGTGCTCCTTGGGTGGGGACGAGCATGGAAGGCGCGTGGGACTGAAGCCTTGAAGACCCCGCAGGCGCCTCTCCTGGACAGACCTCGTGCAGGCGCCTCTCCTGGACCGACCTCGTGCAGGCGCCTCTCCTGGACAGACCTCGTGCAGGCGCCTCTCCTGGACCGACCTCGTGCAGGCGCCGCGCTGGACCGACCTCGTGCAGGCGCCGCGCTGGGCCATGGGGAGAGCGAGAGCCTGGTGTGCCCCTCAGGGAC\t*\tSA:Z:chr2_KI270774v1_alt,105288,-,114M1I27M1I127M,56,13;\tMD:Z:214\tRG:Z:GATKSVContigAlignments\tNM:i:56\tAS:i:142\tXS:i:0\n", true);
        final AlignmentInterval nonCanonical = fromSAMRecordString("asm002362:tig00002\t2064\tchr2_KI270774v1_alt\t105288\t56\t114M1I27M1I127M\t*\t0\t0\tATGCTGGGGAATTTGTGTGCTCCTTGGGTGGGGACGAGCATGGAAGGCGCGTGGGACTGAAGCCTTGAAGACCCCGCAGGCGCCTCTCCTGGACAGACCTCGTGCAGGCGCCTCTCCTGGACCGACCTCGTGCAGGCGCCTCTCCTGGACAGACCTCGTGCAGGCGCCTCTCCTGGACCGACCTCGTGCAGGCGCCGCGCTGGACCGACCTCGTGCAGGCGCCGCGCTGGGCCATGGGGAGAGCGAGAGCCTGGTGTGCCCCTCAGGGAC\t*\tSA:Z:chr2,1422222,-,75M56I139M,60,56;\tMD:Z:94C17G1G6T13T3G1G34A3T9T68T8\tRG:Z:GATKSVContigAlignments\tNM:i:13\tAS:i:179\tXS:i:142", true);
        data.add(new Object[]{Arrays.asList(canonical, nonCanonical), 142, nonCanonical});

        final AlignmentInterval one = fromSAMRecordString("asm008084:tig00048\t16\tchr20\t188506254\t60\t182S253M\t*\t0\t0\tGTTGTAGATTAACTGATGTCCAAATAGGAGGAAATAGCATAAGATCATGGAAGAGAGAGGGCAGTAGATTACCTGATGTCGAAATAGCAGGTAAGAGCATACTGTCCTGGAAGAGAGGGCTGTAGATTACCTGATGTCCAAATAGTAGGTAATATCGTAATGTGCTGGAAGAGAGAGGGCTTTAGATTACCTGATGTCCAAATAGGAGGAAATAGCATAAGGTCCTGGAGGACAAAGGTGTTGTAGATTACATGATGTCCAAATAGGACGTAATAGCATAAGGTCCTGGAAGACAAAGGTGTTGTAGATTACCTGATGTCCAAATAGGAGGTAATAGAATACGGTCCTGGAAGAGAGGGCTGTAAATTACCCGATGTCAAAATAGGAGGTAATAGAATAAGGTCCTGGAGGAGAGGGCTGTAGATTACCTGATGT\t*\tSA:Z:chr4,188507694,-,229M206S,60,22;chr4_KI270789v1_alt,167965,-,122M60I253M,60,62;\tMD:Z:16T99T42A29T6C19C36\tRG:Z:GATKSVContigAlignments\tNM:i:6\tAS:i:223\tXS:i:157", true);
        final AlignmentInterval two = fromSAMRecordString("asm008084:tig00048\t2064\tchr20\t188507694\t60\t229M206H\t*\t0\t0\tGTTGTAGATTAACTGATGTCCAAATAGGAGGAAATAGCATAAGATCATGGAAGAGAGAGGGCAGTAGATTACCTGATGTCGAAATAGCAGGTAAGAGCATACTGTCCTGGAAGAGAGGGCTGTAGATTACCTGATGTCCAAATAGTAGGTAATATCGTAATGTGCTGGAAGAGAGAGGGCTTTAGATTACCTGATGTCCAAATAGGAGGAAATAGCATAAGGTCCTGGA\t*\tSA:Z:chr4,188506254,-,182S253M,60,6;chr4_KI270789v1_alt,167965,-,122M60I253M,60,62;\tMD:Z:11C5A4G24C32C13T6A31G11G8G0G0A3G2C0A1T1G12G19G7T3C0A14\tRG:Z:GATKSVContigAlignments\tNM:i:22\tAS:i:119\tXS:i:58", true);
        final AlignmentInterval three = fromSAMRecordString("asm008084:tig00048\t2064\tchr4_KI270789v1_alt\t167965\t60\t122M60I253M\t*\t0\t0\tGTTGTAGATTAACTGATGTCCAAATAGGAGGAAATAGCATAAGATCATGGAAGAGAGAGGGCAGTAGATTACCTGATGTCGAAATAGCAGGTAAGAGCATACTGTCCTGGAAGAGAGGGCTGTAGATTACCTGATGTCCAAATAGTAGGTAATATCGTAATGTGCTGGAAGAGAGAGGGCTTTAGATTACCTGATGTCCAAATAGGAGGAAATAGCATAAGGTCCTGGAGGACAAAGGTGTTGTAGATTACATGATGTCCAAATAGGACGTAATAGCATAAGGTCCTGGAAGACAAAGGTGTTGTAGATTACCTGATGTCCAAATAGGAGGTAATAGAATACGGTCCTGGAAGAGAGGGCTGTAAATTACCCGATGTCAAAATAGGAGGTAATAGAATAAGGTCCTGGAGGAGAGGGCTGTAGATTACCTGATGT\t*\tSA:Z:chr4,188506254,-,182S253M,60,6;chr4,188507694,-,229M206S,60,22;\tMD:Z:311T6C56\tRG:Z:GATKSVContigAlignments\tNM:i:62\tAS:i:289\tXS:i:223", true);
        data.add(new Object[]{Arrays.asList(one, two, three), 223, three});

        final AlignmentInterval normalOne = fromSAMRecordString("asm000012:tig00003\t2048\tchr1\t933803\t60\t317M302H\t*\t0\t0\tTCCTGGAAGGTTTAGAGCCCAGCCTGGGAGTCTTTGGTGCTGAAACGGATCTGCTTAGGGGCAGCCTTGGATTAGCCCAGCTCCAGCCAGCCCAGGTCAGGGGAGCCGGGAGCTATTTAACGAGGTTTAGGGTAGGCTCCCAGGTCACTGCGCAGGACTGCTCCGTTACAGGTGGGCAGGGGAGGCTGCTCCGTTACAGGTGGGCAGGGGAGGCGGCTCCGTTACAGGTGGGCAGGGGAGGCGGCTGCGTTACAGGTGTGCAGGGGAGGCGGCTGCGTTACAGGTGGGCGGGGGAGGCGGCTGCGTTACAGGTGGGC\t*\tSA:Z:chr1,934806,+,163S456M,60,8;\tMD:Z:140T45G3G27G39G30A12C14\tRG:Z:GATKSVContigAlignments\tNM:i:7\tAS:i:282\tXS:i:0", true);
        final AlignmentInterval normalTwo = fromSAMRecordString("asm000012:tig00003\t0\tchr1\t934806\t60\t163S456M\t*\t0\t0\tTCCTGGAAGGTTTAGAGCCCAGCCTGGGAGTCTTTGGTGCTGAAACGGATCTGCTTAGGGGCAGCCTTGGATTAGCCCAGCTCCAGCCAGCCCAGGTCAGGGGAGCCGGGAGCTATTTAACGAGGTTTAGGGTAGGCTCCCAGGTCACTGCGCAGGACTGCTCCGTTACAGGTGGGCAGGGGAGGCTGCTCCGTTACAGGTGGGCAGGGGAGGCGGCTCCGTTACAGGTGGGCAGGGGAGGCGGCTGCGTTACAGGTGTGCAGGGGAGGCGGCTGCGTTACAGGTGGGCGGGGGAGGCGGCTGCGTTACAGGTGGGCGGGCGGTGCTGCAGGAGGACTGCTCAGGGAGTGGCGCCTGGACCCTGAGCCCCTTCTCTGCTGACTGGGGAGAGGCTCACGGAACCGGGAAGGGGTGGAGGGCCGTGCTCCACACAGTTCGTCTCATTGCTCTCTGGGACTCTGTGGATGTGGGATTGGGCTGAATTAGCAAGAAGAGGAGAAATGAGGGAAGAAAAGAGTTAAATGCATGTTGATTCCAAGCCCCCGCCTGCCGGGGGGACAGCGGGAGGTTGGAGCACGCAGCCCTGGTGCCTGGTGCGAGCTGCACGTGTCTGCCGGTG\t*\tSA:Z:chr1,933803,+,317M302S,60,7;\tMD:Z:14G8G18G12G14G24G2G32G324\tRG:Z:GATKSVContigAlignments\tNM:i:8\tAS:i:416\tXS:i:147", true);
        data.add(new Object[]{Arrays.asList(normalOne, normalTwo), 416, null});

        final AlignedContig contigWithNoGoodCanonicalMapping = fromPrimarySAMRecordString("asm012289:tig00070\t0\tchr22\t11212173\t9\t1435S209M1I125M2I43M2I45M1588S\t*\t0\t0\tATATAAAATATCAAAATACATCAAATACATATTATGTACTCTACATAAAGTATCAAAAAGTACACAAAATACATATTATATAATGTACATAAAATATCAAACAGTACACCAAATACATATTATATACTGTACATAAAGTATCAAAAGGTACACCAAATACATACTATATACTGTACATAATATATCAAAGTTCAAAAAATACATATTATATTGTGTTCATAAAATATCAAGTACACAAAATACATATTATATACTGTACAGAAAATATCAAATAGTGTACCAAATATTTATTTTATATTATAAATAAAATACCAAATAGTACACCAAATATATATTACATTCTCTATATAAAATACCAAAATATACAACAAATACATATTATATACTGTACATATATTATCAATTACTACACGAAATACATATTATAAACTTTACCTAAAATATCAAAGTACAACGAATACATTTCATATACTGCACATAAAATATCAAATAGTACACCAAATACATGTTATATACAGTACATAAAATATGAATGTACACAAAATACATATTATATACTGTACATAAAATATCGAAGTACACAAAATACATATTATATACTGTACACAAAATATCAAAGTACACAAAATACATATTATATACTATACATAGTATATCAAAGTACAAAAAATACATATATACTGTACATAAAATGTGAAAGTTACACAAAATACATATTATATACCATAGATAAAATATCAAAGTACACAAAATACATATTCTATACAGTACATAAAATATCAAAGAACACCAAATACATATTATATACTGTACATAAGATATCAAAAAGTACACAGAATTCATAATACATTCTGTATATAAAATATCAAAGTACACTAAATACATATTATTTAGTGTACATAAAATATAAAAGTACACAGAATACATATTATATACTGTACATAAGATATCAAAGAGTACACGAAATACATATTATATACTGTACATAAAATATCAAAAAGTACACCAAATACATATTATATATTGTACATAAAATATCAAAAACTACACCAAATACATATTATATACTGTACATAATATATCAAAGTACAAAAAATACATATTATATTGTGTACATAAAATATCAAAAACTACACCAAATACATATTATATACTGTACATAATATATCAAAGTACAAAAAATACATATTATATTGTGTACATAAAATATCAAGGTACACAAAATACATATTATATACTGTATATAAAATATCAAATTTCACAAAATACATATTGTATACCACACATAAGATATCAAAGTACACTAAATACATATTTTACACTGTTTATAATATATCAAAGTACAACAAAAACATATTATACACTGTACATAAAATATAAAAGTACGCAAAATACATATTACATTCTGTACATAAAATATAAAAGTACACAAAAAACATATTATATACTCTACATAAAATATCAAATAGTAAACCAAATACATATTATATACTGTACATAATGAATCAAAGGGTATACCAAATACATATTTTATACTGTACATAAATTATCAAAAAGTACACCAAATACATATTATATAATGTACATAAAATATCAAAAAGTACACCAAATACATATTATATACTGTACATAAAATATCAAAAAGTACACGAAATACATATTATATACTATTCATAAAATATCAAAATGTACACCAAATACATATCATATAGTGTACTTAAAATATCAAAAAGTACACCAAATACATATTATATACCATACATAAAATATGATAAAGTACACCAAATACATATTATATACCATACATAAAATATCTAAAAGTACACCAAATACATATTATATACTGTACAGAAAATATCAAATAGTACACCAAATATTTATTATATATTATACATAAAATACCAAATAGTACACCAAATATATATTACATACTCTATATAAAATAGCAAAATATACACCAAATACATATTATATACTGTACGTATATTATCAATCACTACATGAAATACATATAATAAACTTTATATAAAATATCAAAGTACACCAAATACATATTATATACTGTACATAAAATATCAAAAAGTACACCAAATACATACTATATACAGTACATAAAATATGAATGTACACAAAATACAAATTATATACTGTACATAAAATTTCAAAGTACACAAAATATATATTATATACTGTACATAAAATACCAAAGTACACAAAATACATATTATATACTTTACATAATATATCAAAGTCCACAAAATACATATATACTGAACAAAAAATGTCAACGTTACACAAAATACATATTATATACCATAGATAAAATATCAAAGTACACAGAATACATGTTATATACTGTACATAAGATATCAAACAGTACACAAAATACATATTACATACTGTAGGTAAAGTATCAAAAATTACACCAAATACATATTATATCCTGTACATATAATATCAAAATGTACACCAAGTACATATTATATACTGTACATAAAATATCAAAAAGTACACCAAATACATAATATATACTGTACATAAAATATCAGAAACTACAGGAAATACATAGTATATACTCTACATAATATATGAAAGTGCAAAAAATACATATTATATAGTGTACATAGAACATCAAGCTACACAAAATACATATTATATAATGTACATAAAATATCAAAGTTCACAAAATACATATTGTATACCGCACATAAAATATCAAAGTACACTAAATCCATATTTTACACTGTACATAAAATATCAAAGTACAACAAATACATATTATGCACTGTACAGAAAATATAAAAGTACACAAAATACATATTATATTCTGTACATAAAATATAAAAGCACACAAATATCATATTATATACTCTACATAAAATATCAAATAGTACACCAAATACATATTATTTACTGTACATAAAGTATCAAAACGTACACCAAATACATATTATATACTGTAAATAAATTATCAAAAAGTACACCAAATACACATTATACACTGTGTATAAAATATTAAAAAATTACACCAAACATATATTATATACTGTATATAATATATATATAGTATATACATGTACTATAAATATATATATAAATATGGTACAATATGTGTATTTTTGATATTTTATGTACAGTAGATAATATATATTTGGGGTACTTTTATATTTTAGGTACAGTATATAATATATATTTGGAGTACCTTCATATTTTATGTAAAGAATATAATATATACTTGATGTACCTTCATATTTTAAGTCCAGTATATAATATATAGTTTTGGTACTTTGATATTTTATGTACAGTAAATAATATATAGCTTGAGTACTTTGATTTTTATGTACAGTTTATTATAAATTTTTGGGTACTTTCATATTTTATATACAGAATATAATACATAATTTGGGTGCTTTGATATATTATGTATAGTATATAAAACATACTTTTTGT\t*\tSA:Z:chr22,11212247,+,2329S134M1I299M687S,9,53;chr8,43239662,+,2025S52M2I173M1198S,6,19;chr8,43237624,-,2342S63M2I86M2I104M851S,0,24;chr8,43241450,+,369S34M2I84M2I183M3D25M1I126M2624S,6,62;chr14,16092705,-,3313S116M21S,9,8;chr22,11213337,+,2708S134M608S,9,12;chr14,16093049,-,2194S113M1143S,9,9;chr4,49711819,-,14S102M3334S,9,7;chr14,16093049,-,2278S41M2I72M1057S,9,10;chr22,11213752,+,1353S90M2007S,9,7;chr14,16095303,-,540S52M2858S,9,0;chrUn_KI270467v1,2467,+,3245S73M132S,9,5;chrUn_JTFH01001739v1_decoy,939,-,354M2D1883M86I1058M2I67M,60,98;\tMD:Z:11G2G2T14T3C10A7G5T4A0A0T6A0A3C30A21T6A3C44A24C2G15G1A12T1T0A17T5C5A8G10G14G3T0G12C3A18C4T0G35C10T11\tRG:Z:GATKSVContigAlignments\tNM:i:46\tAS:i:164\tXS:i:152", true);
        data.add(new Object[]{contigWithNoGoodCanonicalMapping.getAlignments(), 164, (AlignmentInterval) null});
        return data.toArray(new Object[data.size()][]);
    }
    @Test(groups = "sv", dataProvider = "forGetBetterNonCanonicalMapping")
    public void testGetBetterNonCanonicalMapping(final List<AlignmentInterval> configuration, final int maxCanonicalAS,
                                                 final AlignmentInterval expectedOutput) {
        final AlignmentInterval result = AlignedContig
                .getBetterNonCanonicalMapping(b38_canonicalChromosomes, configuration, maxCanonicalAS);
        Assert.assertEquals(result, expectedOutput);
    }

    @DataProvider
    private Object[][] forFilterSecondaryConfigurationsByMappingQualityThreshold() {

        final List<Object[]> data = new ArrayList<>(20);

        AlignmentInterval intervalOne = new AlignmentInterval(
                new SimpleInterval("chr21", 100001, 100100),
                1, 100, TextCigarCodec.decode("100M220S"),
                true, 60, 0, 100, ContigAlignmentsModifier.AlnModType.NONE);
        AlignmentInterval intervalTwo = new AlignmentInterval(
                new SimpleInterval("chr21", 100099, 100122),
                99, 122, TextCigarCodec.decode("98S24M78S"),
                true, 10, 3, 241, ContigAlignmentsModifier.AlnModType.NONE);
        AlignmentInterval intervalThree = new AlignmentInterval(
                new SimpleInterval("chr21", 100123, 100200),
                223, 300,  TextCigarCodec.decode("222S78M"),
                true, 60, 0, 78, ContigAlignmentsModifier.AlnModType.NONE);
        final AlignedContig.GoodAndBadMappings rep1 =
                new AlignedContig.GoodAndBadMappings(Arrays.asList(intervalOne, intervalThree),
                        Collections.singletonList(intervalThree));
        final AlignedContig.GoodAndBadMappings rep2 =
                new AlignedContig.GoodAndBadMappings(Arrays.asList(intervalOne, intervalTwo, intervalThree),
                        Collections.emptyList());

        data.add(new Object[]{Arrays.asList(rep1, rep2), 0, Arrays.asList(rep1, rep2)});

        data.add(new Object[]{Arrays.asList(rep1, rep2), 10, Collections.singletonList(rep1)});

        final AlignedContig alignedContig = fromPrimarySAMRecordString("asm031090:tig00000\t16\tchr5\t49659827\t60\t332S112M161S\t*\t0\t0\tCATTCCGTTCCGTTCCATTCCATTCCATTCCATTCTATTCGGGTTAATTCCATTCCATTCCATTCGATTGCAATCGAGTTGATTCCATTCCCTAACATTCCATTCCATTCCATTCCATTCCATTCCATTCCATTCCTTTCCATTCCATTACGGATGATTCCATTCCATTGCATTCCATTCCATTCCATTCCCCTGTACTCGGGTTGATTCCATTCCATTGCATTCCAATCCATGCCCTTCCACTCGTGTTGATTCCATTCTTTCCATTCCATTCAAGTTGAATCCATTCCATTGCAATCCATTCCATTCGATTCCATTCGATTGCACTCGGGTTGATTCCATTCCATTGCATTCCATTCCATTCCATTCCATTCCATTCCGTTCCATTCCTTTCCATTACATTCGGATTGATTCTATTCAATTCCCTTACACTCCATTACATTCCATTTCATTCCGGTAGTTTTCACTCCATTCCATTCCATTTCTCTCCATTCCATTGCACTCGGGTTGATTCCATTCCATTGCATTCCATTCCATTTGGGTAGTTTCCACTCCATTCCATTCCATTTCTCTCCATTCCATTGCACTCGGGTTGATTCCATTCC\t*\tSA:Z:chr22_KI270736v1_random,101512,+,455S56M94S,0,1;chr10,41903518,+,372S74M159S,48,7;chr20,31162579,+,37S59M509S,0,5;chr20,31188805,+,298S43M264S,0,2;chr4,49639434,+,331S37M237S,60,1;chrUn_KI270519v1,137524,+,101S37M467S,3,1;chrUn_KN707896v1_decoy,6014,-,81M15I253M5D189M67S,0,34;chrUn_KN707896v1_decoy,6436,-,517S88M,60,3;\tMD:Z:58A7C7G18T12C5\tRG:Z:GATKSVContigAlignments\tNM:i:5\tAS:i:87\tXS:i:55",
                true);
        final List<AlignedContig.GoodAndBadMappings> goodAndBadMappings = alignedContig.pickBestConfigurations(
                new HashSet<>(Arrays.asList("chr4", "chr5", "chr10", "chr20", "")), 0.0);
        final List<AlignmentInterval> goodAfterTieBreak = fromPrimarySAMRecordString("asm031090:tig00000\t16\tchr5\t49659827\t60\t332S112M161S\t*\t0\t0\tCATTCCGTTCCGTTCCATTCCATTCCATTCCATTCTATTCGGGTTAATTCCATTCCATTCCATTCGATTGCAATCGAGTTGATTCCATTCCCTAACATTCCATTCCATTCCATTCCATTCCATTCCATTCCATTCCTTTCCATTCCATTACGGATGATTCCATTCCATTGCATTCCATTCCATTCCATTCCCCTGTACTCGGGTTGATTCCATTCCATTGCATTCCAATCCATGCCCTTCCACTCGTGTTGATTCCATTCTTTCCATTCCATTCAAGTTGAATCCATTCCATTGCAATCCATTCCATTCGATTCCATTCGATTGCACTCGGGTTGATTCCATTCCATTGCATTCCATTCCATTCCATTCCATTCCATTCCGTTCCATTCCTTTCCATTACATTCGGATTGATTCTATTCAATTCCCTTACACTCCATTACATTCCATTTCATTCCGGTAGTTTTCACTCCATTCCATTCCATTTCTCTCCATTCCATTGCACTCGGGTTGATTCCATTCCATTGCATTCCATTCCATTTGGGTAGTTTCCACTCCATTCCATTCCATTTCTCTCCATTCCATTGCACTCGGGTTGATTCCATTCC\t*\tSA:Z:chr10,41903518,+,372S74M159S,48,7;chr4,49639434,+,331S37M237S,60,1;chrUn_KI270519v1,137524,+,101S37M467S,3,1;chrUn_KN707896v1_decoy,6436,-,517S88M,60,3;\tMD:Z:58A7C7G18T12C5\tRG:Z:GATKSVContigAlignments\tNM:i:5\tAS:i:87\tXS:i:55",
                true).getAlignments();
        final ArrayList<AlignmentInterval> copy = new ArrayList<>(alignedContig.getAlignments());
        copy.removeAll(goodAfterTieBreak);
        data.add(new Object[]{goodAndBadMappings, 0, Collections.singletonList(new AlignedContig.GoodAndBadMappings(goodAfterTieBreak, copy))});

        return data.toArray(new Object[data.size()][]);
    }
    @Test(groups = "sv", dataProvider = "forFilterSecondaryConfigurationsByMappingQualityThreshold")
    public void testFilterSecondaryConfigurationsByMappingQualityThreshold(final List<AlignedContig.GoodAndBadMappings> representations,
                                                                           final int threshold,
                                                                           final List<AlignedContig.GoodAndBadMappings> expectedResult) {

        Assert.assertEquals(AlignedContig.filterSecondaryConfigurationsByMappingQualityThreshold(representations, threshold),
                            expectedResult);
    }

    @DataProvider
    private Object[][] forMiscFunctions() {

        final List<Object[]> data = new ArrayList<>(20);

        AlignmentInterval intervalOne = new AlignmentInterval(new SimpleInterval("chr21", 1948156, 1948936),
                1, 787, TextCigarCodec.decode("257M4I182M2I342M361S"), true, 60, 8, 733, ContigAlignmentsModifier.AlnModType.NONE);
        AlignmentInterval intervalTwo = new AlignmentInterval(new SimpleInterval("chr21", 1948935, 1949190),
                893, 1148, TextCigarCodec.decode("892H256M"), true, 60, 3, 241, ContigAlignmentsModifier.AlnModType.NONE);
        AlignedContig contig = new AlignedContig("asm000063:tig00003", "CCACTGTGCCCGGCCAAGGGTCCCCGGTTCTGAAAGTGGAAGGGGTGCGGCTGCCTCAGGAGTCACCACGGCAACAAGAACCTGGACCTGAGCGCAGGTGGTCAGATTCTGGGGCCAGCAGCTTTTTGGTTTTTAGAGACGAGGTCTCACTCTGTTGCCCAGGCTGGAGTGCAGTGGTGCGATCACTGCACCCTGCAGCCTCGGCCTCCTGGTTTCAAGTGACCACAGATGCATGCAGCCATGCTTGGCATATATAAATATATATATATATATATATTTATGTGTATATTGGTAGAGACATGGTCTTGTTATATTGCCCAGGCTGATCGCAAACATCTGCTTAAGCGATCCTCCTGCGTTGGCTCTCCAAAGTATTGGGATTATAGGCATGAGCTACCATGGCCTGGCCTCCTTATTCTAGTCTTTTCTTTCCTTTCTTCTTGTTTTTTTTTTTTTTTTGGCAGGGTCTCACTCTGTCACCCAGGCTGCAGTGCAGTGGTGTGATCACAGCTCACTGCAGCCTCAACTTCCCAGGCTCAAGCGATCCTCCCGGCTCAGCATCCTGAGTAGCTGGGACTACAGATGCATGTCACCACGCCTGGCTAAATTTTCTTCTTTGTAGATATGGGGTCTCACCATGTAGTACTTTTCAATGTATTAAGCATCCTTATTTGATATTTGATGCCTGATAATACCCATGTCTGAACCATGCAAGATTGCTGCAATTCCTTCCTTCCTTCCCTCCCTCCTTCCCTTCCTTCCTTCCCTTTCCTTCCTTCCTCTTTCCCTCCCTTCTTTCCTTCCCTTTCCCTCCCTCCCTTCCTTCCTCTTTCCTTCCTTCCTTTCCCTCCCTTACTCCTTCCTTCCCTTCCCCTTCCTTCTTCCTTCTCTCCCTCCCTCCCTTCCCCTCCCTTACTCCCTTCCTTCCTCCTTCCCTCCCTCCTTTCCTTCATTCCCTTCCTTCCCCTTCCCCTTCCTTCCTTCTCTCCCTCCCTCCTTCCTTCCCTCCTTTCCTTCCTTCCTTCCTTTCCTTTCCCTCCTTCCTCCCTCCCTCCTTTCCTTCCTTCCTTTCCTTTCCTCCCTTCCCTCCCTCCCTCCCTCCCTTCCTTCCCCTCCCTCCCTCCTTTCCTTCTTTCGACAGAGTCTTG".getBytes(),
                Arrays.asList(intervalOne, intervalTwo));
        data.add(new Object[]{contig, Arrays.asList(intervalOne), Arrays.asList(intervalOne, intervalTwo), 1, 2});

        intervalOne = new AlignmentInterval(new SimpleInterval("chr2", 1422222, 1422435),
                1, 270,  TextCigarCodec.decode("75M56I139M"), false, 60, 56, 142, ContigAlignmentsModifier.AlnModType.NONE);
        intervalTwo = new AlignmentInterval(new SimpleInterval("chr2_KI270774v1_alt", 105288, 105555),
                1, 270,  TextCigarCodec.decode("114M1I27M1I127M"), false, 56, 13, 179, ContigAlignmentsModifier.AlnModType.NONE);
        contig = new AlignedContig("asm002608:tig00001", "ATGCTGGGGAATTTGTGTGCTCCTTGGGTGGGGACGAGCATGGAAGGCGCGTGGGACTGAAGCCTTGAAGACCCCGCAGGCGCCTCTCCTGGACAGACCTCGTGCAGGCGCCTCTCCTGGACCGACCTCGTGCAGGCGCCTCTCCTGGACAGACCTCGTGCAGGCGCCTCTCCTGGACCGACCTCGTGCAGGCGCCGCGCTGGACCGACCTCGTGCAGGCGCCGCGCTGGGCCATGGGGAGAGCGAGAGCCTGGTGTGCCCCTCAGGGAC".getBytes(),
                Arrays.asList(intervalOne, intervalTwo));
        data.add(new Object[]{contig, Arrays.asList(intervalTwo), Arrays.asList(intervalOne), 3, 1});

        intervalOne = new AlignmentInterval(new SimpleInterval("chr21", 30374719, 30375721),
                1, 1002,  TextCigarCodec.decode("966M1D36M2362H"), true, 60, 6, 960, ContigAlignmentsModifier.AlnModType.NONE);
        intervalTwo = new AlignmentInterval(new SimpleInterval("chr21", 30375922, 30378473),
                826, 3364,  TextCigarCodec.decode("825S33M1D1047M7D553M5D906M"), true, 60, 24, 2423, ContigAlignmentsModifier.AlnModType.NONE);
        AlignmentInterval intervalThree = new AlignmentInterval(new SimpleInterval("chr1_KI270760v1_alt", 22529, 23531),
                1, 1002,  TextCigarCodec.decode("966M1D36M2362H"), true, 14, 3, 975, ContigAlignmentsModifier.AlnModType.NONE);
        AlignmentInterval intervalFour = new AlignmentInterval(new SimpleInterval("chr1_KI270760v1_alt", 23681, 26220),
                826, 3364,  TextCigarCodec.decode("825H33M1D2506M"), true, 60, 2, 2517, ContigAlignmentsModifier.AlnModType.NONE);
        contig = new AlignedContig("asm027070:tig00000", "GAGCCCATCTCCTTGACTGTGGCTCTGATGCTGCCTCCACACTGGGATCTCTCTGCTCTCTTCACCTCATACCTCCTTCCCCCCACCTCACCCCATCGCCCCCGTTCTTGATCCTGCAATTGTAGAAACAGAAAGTTGGCTGATTTCTTGGGCCCGCAAATTGCCCAACAGGGAGACTGGGTGGGCGGCCCCCGCTTCCACTCCATCGCCCACCCTGATGCATCGTCTGACACTTTCAATTTATTTTTCAATTCCTCTACCATCAGAAATGACGATTAGATTTCCAGCATAAATACCGCCTTACCAAACTGAATTAATCACGGCAAGGAGGGGCACACACAGGCTCCAGCAGCCTGGGCAGAACATCCCCAGCATTAACCCTTCCGTCCTCACCCAGGCCCCCACCAGCAGGACGGAGGCTCCAGGCCTCACAGAAGACGCCACTCAAAATATCACTGGGGTCACCTAATCCCATCCCCCTTACCCTTTGCAGCCTCCCTCCTGTGGGAGTTCCTAGGAAGTGTCTTGCCCAAAGCCATCCACTCCATCAGGGCAGAGTCAGAGACACTGGCCCCTCATCTCCAGCCCCATCAGGGAAGGAGGCTCCATCCACATCCAGGACAAGATGTGGGAGTATCCGGGGTTTGGCGTTGTCCAGGACACATACGGGACGGGACTCCTGCAGACCCGAGGGTGGGGGCACCCAGTGATCACAGGGCCTGAACTGAAAGGGGTCTTGGAGAGACCTGGAGGCAGGTTCCAACCCTTGCCCCACAAACAAGACCATCACCCCTCTTTGCTGAGACTGTTCATTGCTCAGTCCAACAACCACAGCTCAGGTTGACCTCCAGCCTCCCCACTTCTCCACCTCCCTGACTCCAACCACAGCTCAGGGTGACCTCCAGCCTCCCCACTTCTCCACCTCCCTGACTCCAACCACAGCTCAGGGTGACATCCAGCCTCCCCACTTCTCCACCTCCCTGACTCCAGCCACAGCTCAGGCTCCTTCCTATGAGACCCCCATGGCCTCTCACAGCCTCTCCACTTCTATGCCTGTTCTCACCCAATCCCCATCCCTCAGCAGTCATCACCTCAAAATGCAAACACTGTCCTATGGTTTCCTGGCTCAGAACCCATCGGGCCCTCCTCTGCTCTCAAATCAGGCCCCCACCCTTCAAGGCCATGAGGACTGGGCTGGCCTGGCCCCTACCGGTCAGTGCACTCCCCCATCCTGGCTGGGTTGTCTCCTCTTTCTCCTTCAAGTTTTTCTATTTAAAATTCCCCTCCTCAGAGAACCTTCTCTGGCCACCATCCCCCAATCTAAATTAGGTTCTCCCTCCTAAGGTTCTTTCTCAAATCCATTTCCTTTCCTTCTGAGCACTTAAGCGAGCGATAATTACACACTAACTTGTGTAATTTGTTTAATAGGATCTTTGGGACAGAGACTTTATCTGACTCGCTTGATGCTGCAGCTGCTAGAACCCAGACCGTAATGTAGTGGGAGCTCAGTGCAGACTTTTGAAGGAGTAAGTGAGTAAAAGAACAACAAGCCCCTCTTGGTGCCCACCAAGTGCCAAGCTGAGACTGGGCCCTGGAGCTGGAGTCAAGATGTGGACCTGGCCTTGGTGTGCTGGGCCCTAACAGATGAGTAGGAGTTTGCCGAGCACTGAAGGTGGGGTTGACATGACCAACTTCTGAGAGGCACTCTTTGCCTCTGGATGGCCCCTTCCCAGTCACCCCAAAAGGAAGCCCTTGCCCTTTCAAAAGTGGTGAATGTGGTGGTTCAGATCGGTAGGTGTTCCTATGAATAGGTGAGGGGCCAGGCTTCAGGTCAGTTGAACCTGGGTTTGAATCCTGATTTTGCTCTTGGTACTAGGGCAGGTCACTGAGACGCTCTGAGCCTCTCTGCTCCAGGATGAGGATCCCTTCATCCATGCTCACTCAAAGTCCTGCCCACCAGGATGGAGGCAGACAGGCTGCAATGCCCTCCCCTCTCAGTGGGGGAAAAATACCAGGTCAGGCAGCCAGCAGCCGAGAATGCCAGGCAGAGCAAAGGTGTCCTAAGGGATGGACAGAATAAGGGCTTGAGAGCCTAGCCAAGGGTGAGGCTAGGAGAGGCTTCCCGGAGGACGAGGCAAGTCAGAGCTCTTTGCCTCTTACTCCCATGACTGTGGGTGCCTTTCTCCTCCTCCTCTCATTCTCTCTCCTTTCCAGCTCCTGCTCTGCTCATTTCTTCACCTCAGTCTCTCTGCCCCGACAGGAGCCCTGAGGGACACAACCCCGTCCCGAGGAATGTATCTGCCCACTTCCAGCAGGTTCCTGGAGGCCCTCTAAATTCCCCTTCCCCCCAAAGTCATCTCCCAACACTGCTGCTCCCAGGGTGGGACGCCTGCTGCTGCACCTCCACACACGTGCACACACCCAGCCAGGTGCAGACAGCGTGGGCAGTGCAGAGGGGAGGGCTGGGGATTAAGGAGTTCGTGTTCTTGAGCAGCCTGGAAAGCAGCAGGGCTTCCACAGGAGCCGCCCCTGCCCTCACCCCTGCCCAGTAGGGTTAAGGGGCTGGCTTAGATGTCACCCCAAGCCAAGGCTGTCCTTCTCAGAGGCTCCTTCCCAGCTCCCCTGAGTGGGTCAGTCCCTTCCCCTCTCTGAGCCCCTCTTTCCTCTTCTGTAAAGCAGACTCAGTGATGTTGCTCAGAGGATTGAAGGACAAAGAAAAGCAACACAATGGACAGCAGGGATTTGCAAACAGCCGGGTGCTGTACCCAAGACAGGGTATTGCTGGTGATGTCTGATGGATGGGGAGTTGAAAGACTCAGCTGTCACTGGGCAGCTGGGTCTGGTTCCCCTGAGTCATTCGTAATTCACCAACCCAGTCTATAGAAGCTTATTAAGCACTTATTGTGTGCCATGCTCCATGCAAGGGCCAAAGACACCATGAGCAGAGCCAGACCCCACCCTCAGGTTCCCCCATGGGATGGGGTTAGCCAGATGACCTGAAGGCCTCTCCAGCCAGCTCAACCCCCTTAATCCAGAATTACTCCCTGTGCCAGGCTGACGGTGTGGCCAGAGAGGCCAGGGCCTGGGAGGGGGCCTGGCAGTGGGTGGTGGGAAGAGATGGAGTGGCTGTGTCAGGGGAAGGAGAGAGCAGGTTGTTCCTGTACAGGTTTCGCTCCTCGGATAGGGGGCTGCAATGACAGCTTCCAGGAAAGACCAGGCAAGTGCCTCACCCCATCCATTCTTGCTCACCCCTGCGGCCTCTTGGCCAATGGCTGCTGTGACCCTGTCCTCCTCTGGGAATCTGGTCTCGGGGAGGAGCCCTGGACCCTGACATTGACTAGAAACCTGACCCCATGTCTGAGCA".getBytes(),
                Arrays.asList(intervalOne, intervalTwo, intervalThree, intervalFour));
        data.add(new Object[]{contig, Arrays.asList(intervalOne, intervalFour), Arrays.asList(intervalThree, intervalFour), 2, 2});

        // this is a case where {intervalOne} is equally good with {intervalOne, intervalTwo}, but somehow the score for latter case is tiny bit better than the first
        intervalOne = new AlignmentInterval(new SimpleInterval("chr20", 60230348, 60231029),
                1, 682, TextCigarCodec.decode("682M"), false, 57, 68, 342, ContigAlignmentsModifier.AlnModType.NONE);
        intervalTwo = new AlignmentInterval(new SimpleInterval("chrUn_JTFH01001804v1_decoy", 3674, 4300),
                1, 627, TextCigarCodec.decode("627M55H"), true, 60, 1, 622, ContigAlignmentsModifier.AlnModType.NONE);
        contig = new AlignedContig("asm005003:tig00056", "AAAACTGCTCTATCAGAAGAAAGGTTAAGCTCTGAGAGTTGAACGCACACATCACAAAGTAGTTTCTAAGAATCATTCTGTCTGGTTTTCCTATGAAGATATTGCCTTTTCTACCATAGGCCTCAAACGGCACTAAATATCCTCTTTGAAATCCTTCAAAAAGAGACTCTCAAAACTTCTCTATCGAAAGGAAGGTTCAACACCGTGAGTTGAAAGCACACATCAGAAAGAAGTTTCTGAGAAGTATTCTGTCTAGTTTTATAGGAAGAAATCACGTTTCAAAAGAAGGCCACAAAGAGGTCCAAATATCCACTTGCAGATTCTACAAAAAGAGTGTTTCAAAACTGCTCTATCAAGAGAAATGTTCATCTCCGTGAGGTGAATGCAAATATTTCAATGTAGTTTCTGACAGTGCTTCTGTCTAGTTTTTATGTGAAGATATTTCCTTTTCTACCGTAGGCCTCAAAACACTCTCAATATACACTTGCAAATTCCACAAAAAGAGTGATTCAAAACTGCTCTATCAAAAGAAATTTTAAACGCTGTAAGCTGAATGCACACATCACAAAGTAGTTTCTGAGAATGATTCTGTCTAGTTTTTCTATGAAGATATTTCCTTTTCTACCATAGGCCTTGAAGCGCTCTAAATATCCACTTGGAAATTCTACAAAAAGAGTATTTC".getBytes(),
                Arrays.asList(intervalOne, intervalTwo));
        data.add(new Object[]{contig, Arrays.asList(intervalOne, intervalTwo), Arrays.asList(intervalOne), 2, 1});

        return data.toArray(new Object[data.size()][]);
    }
    @Test(dataProvider = "forMiscFunctions", groups = "sv")
    public void testMiscFunctions(final AlignedContig contig,
                                  final List<AlignmentInterval> configuration,
                                  final List<AlignmentInterval> configurationEquallyGoodOrBetter,
                                  final int expectedConfigurationCount,
                                  final int expectedAICount) {

        final double scoreOne = AlignedContig.computeScoreOfConfiguration(configuration, b38_canonicalChromosomes, 60);
        final double equallyGoodOrBetterScore = AlignedContig.computeScoreOfConfiguration(configurationEquallyGoodOrBetter, b38_canonicalChromosomes, 60);
        assertTrue( scoreOne < equallyGoodOrBetterScore || scoreOne - equallyGoodOrBetterScore <= Math.ulp(equallyGoodOrBetterScore));

        assertEquals(contig.pickBestConfigurations(b38_canonicalChromosomes, 0.0).size(), expectedConfigurationCount);

        if (expectedConfigurationCount == 1) {
            final List<AlignmentInterval> alignments = contig.pickAndFilterConfigurations(b38_canonicalChromosomes, 0.0).get(0).getGoodMappings();
            assertEquals(alignments.size(), expectedAICount);
        }
    }

    // step 3: reconstruction from the picked configurations ===========================================================

    // functionality group 3.1: split gaps
    @DataProvider
    private Object[] forSplitGapsAndKeepChildrenTogether() {
        final List<Object[]> data = new ArrayList<>(20);

        AlignmentInterval noGap;
        AlignmentInterval gapped;
        // case one: gapped alignment provides worse coverage
        noGap = new AlignmentInterval(new SimpleInterval("chr1", 1_000_001, 1_000_950),
                1, 950, TextCigarCodec.decode("950M50S"),
                true, 60, 0, 950, ContigAlignmentsModifier.AlnModType.NONE);
        gapped = new AlignmentInterval(new SimpleInterval("chr1", 1_000_101, 1_001_200),
                101, 1000, TextCigarCodec.decode("100S300M200D600M"),
                true, 60, 200, 900, ContigAlignmentsModifier.AlnModType.NONE);
        data.add(new Object[]{new Tuple2<>(noGap, gapped),
                false,
                new AlignedContig.GoodAndBadMappings(Collections.singletonList(noGap),
                        Arrays.asList(new AlignmentInterval(new SimpleInterval("chr1", 1_000_101, 1_000_400), 101, 400, TextCigarCodec.decode("100S300M600S"), true, 60, AlignmentInterval.NO_NM, AlignmentInterval.NO_AS, ContigAlignmentsModifier.AlnModType.FROM_SPLIT_GAPPED_ALIGNMENT),
                                new AlignmentInterval(new SimpleInterval("chr1", 1_000_601, 1_001_200), 401, 1000, TextCigarCodec.decode("400S600M"), true, 60, AlignmentInterval.NO_NM, AlignmentInterval.NO_AS, ContigAlignmentsModifier.AlnModType.FROM_SPLIT_GAPPED_ALIGNMENT)))
        });

        noGap = new AlignmentInterval(new SimpleInterval("chrUn_JTFH01000492v1_decoy", 501, 1597),
                1, 1097, TextCigarCodec.decode("1097M6H"),
                true, 60, 1, 1092, ContigAlignmentsModifier.AlnModType.NONE);
        gapped = new AlignmentInterval(new SimpleInterval("chr17", 26962248, 26962806),
                483, 1103, CigarUtils.invertCigar(TextCigarCodec.decode("121M1D142M1I165M62I130M482S")),
                false, 60, 97, 281, ContigAlignmentsModifier.AlnModType.NONE);
        data.add(new Object[]{new Tuple2<>(noGap, gapped),
                false,
                new AlignedContig.GoodAndBadMappings(Collections.singletonList(noGap),
                        Lists.newArrayList(ContigAlignmentsModifier.splitGappedAlignment(gapped, GAPPED_ALIGNMENT_BREAK_DEFAULT_SENSITIVITY, 1103)))
        });

        // case two: gapped alignment provides better coverage with a D-gap
        noGap = new AlignmentInterval(new SimpleInterval("chr1", 1_000_001, 1_000_500),
                1, 500, TextCigarCodec.decode("500M"),
                true, 60, 0, 500, ContigAlignmentsModifier.AlnModType.NONE );

        gapped = new AlignmentInterval(new SimpleInterval("chr1", 1_000_101, 1_001_200),
                101, 1000, TextCigarCodec.decode("100S300M200D600M"),
                true, 60, 200, 900, ContigAlignmentsModifier.AlnModType.NONE);
        data.add(new Object[]{new Tuple2<>(noGap, gapped),
                true,
                new AlignedContig.GoodAndBadMappings(Arrays.asList(new AlignmentInterval(new SimpleInterval("chr1", 1_000_101, 1_000_400), 101, 400, TextCigarCodec.decode("100S300M600S"), true, 60, AlignmentInterval.NO_NM, AlignmentInterval.NO_AS, ContigAlignmentsModifier.AlnModType.FROM_SPLIT_GAPPED_ALIGNMENT),
                        new AlignmentInterval(new SimpleInterval("chr1", 1_000_601, 1_001_200), 401, 1000, TextCigarCodec.decode("400S600M"), true, 60, AlignmentInterval.NO_NM, AlignmentInterval.NO_AS, ContigAlignmentsModifier.AlnModType.FROM_SPLIT_GAPPED_ALIGNMENT)),
                        Collections.singletonList(noGap))
        });

        // case three: gapped alignment provides better coverage with a I-gap
        gapped = new AlignmentInterval(new SimpleInterval("chr1", 1_000_101, 1_000_850),
                101, 1000, TextCigarCodec.decode("100S300M150I450M"),
                true, 60, 150, 750, ContigAlignmentsModifier.AlnModType.NONE);
        data.add(new Object[]{new Tuple2<>(noGap, gapped),
                true,
                new AlignedContig.GoodAndBadMappings(Arrays.asList(new AlignmentInterval(new SimpleInterval("chr1", 1_000_101, 1_000_400), 101, 400, TextCigarCodec.decode("100S300M600S"), true, 60, AlignmentInterval.NO_NM, AlignmentInterval.NO_AS, ContigAlignmentsModifier.AlnModType.FROM_SPLIT_GAPPED_ALIGNMENT),
                        new AlignmentInterval(new SimpleInterval("chr1", 1_000_401, 1_000_850), 551, 1000, TextCigarCodec.decode("550S450M"), true, 60, AlignmentInterval.NO_NM, AlignmentInterval.NO_AS, ContigAlignmentsModifier.AlnModType.FROM_SPLIT_GAPPED_ALIGNMENT)),
                        Collections.singletonList(noGap))
        });

        return data.toArray(new Object[data.size()][]);
    }
    @Test(groups = "sv", dataProvider = "forSplitGapsAndKeepChildrenTogether")
    public void testSplitGapsAndKeepChildrenTogether(final Tuple2<AlignmentInterval, AlignmentInterval> nonGappedAndGappedAlignment,
                                                     final boolean expectedGappedAlignmentOffersBetterCoverage,
                                                     final AlignedContig.GoodAndBadMappings expectedOutput) {

        Assert.assertEquals(
                AlignedContig.gappedAlignmentOffersBetterCoverage(nonGappedAndGappedAlignment._2, nonGappedAndGappedAlignment._1),
                expectedGappedAlignmentOffersBetterCoverage
        );

        final AlignedContig.GoodAndBadMappings actual = AlignedContig.splitGapsAndKeepChildrenTogether(
                new AlignedContig.GoodAndBadMappings(Arrays.asList(nonGappedAndGappedAlignment._1, nonGappedAndGappedAlignment._2)));
        Assert.assertEquals(actual, expectedOutput);
        Assert.assertEquals(actual.hashCode(), expectedOutput.hashCode());
    }

    @DataProvider
    private Object[][] forSplitGapsAndDropContainedAln() {
        final List<Object[]> data = new ArrayList<>(20);

        String samString = "asm005554:tig00000\t0\tchr3\t96003552\t60\t1137M58I16M438S\t*\t00\tTATTAAAACCTAGCATTGATACTGATAGACAATGAGACTGGAGGATTTTTCTCTAGTGAAATAAATGAAGCAAGTAAAAAAATTGTCAGTTATGAAAATAAATCCTTGACCTTCACCATTTTATACTTCAATCACACAAACCCAAAATAAAACACTACTCATGATCACACAGATTCCATTCAACTTTTTAGTACTTTGATTTTAAATATTACTAGAAAATTTATGATTAGTGAATATTAGAAAAAGAAATCAGTTTCTACCATAAAAATACAGAAATACAAATAAGAAGAAAAAAAGAAAATTAGAGAAAACAGAGGCATTATCAAAGGTGATAGAACTTAAAAAAAAACCTTAATTTAATATACCAAGGTATATATGGTAATACATTACATCCATTAATATACCACAAGTTTAGATATCCCTTCTGTTCATATCCCATTGATAAATTTTTATGTAAATTGCCACATCTAAAATCAAGGAAAACAGGTAACATAGTTTGGCTGAATAGCAAATTATTTGAGAGGGGGAAAAGCAAATATACGTATCCACTAAAAAAAAACAAACAAAAACTAATATCATTATAAAAAGAGTTTTAAAAAAAGGAGAACAAAAAATATAGAGATCACAACATCAAATAACTTTTTCAATAAATATGTTCTGGAGAAAGGGATGACAACAGTTGTCTTGTGTTCATTTGCCCCACAAAAAGAGACCACAACGTCAATAAACAAGTTGACTTCAACGCCAGTGACTGAGGAGGTATGGTGGAGAGAACCAGGGGAGCAGCAAAATCTCTGTAGAACATAGAAGCCCAGGATAACACCATAGAGAGGGGAATAAGACATCCCACCCCTGCCACACTGTCTCCCCTGATAGGATTGGCTCAGTGGTGGGAGGACTTATTTTAGGAAAAAGATAAGCTGGAGATCCCTCTTGGTCCCCATTTCTACCATGGACACAAGACAACCTTTCTACAGGAGAGACCCACTGTCCTCACAGCCCCTAAATCCAGTTTGGAGAGTTCTTAAAAGTTTACACCATGCATCCCCCACTCTCATGATCTAAAAATTATACTTTTTTATATATGTATGCATACATATATAATTTTTATATATGTATGCATACATATATAATTTTTATATATGTATGCATACATATATAATTTTTATATATGTATGCATACATATAATTTTTATATATGTATGCATACATATATAATTTTTATATGTATGCATACATATATAATTTTTTATATGTATGCATACATATATAATTTTTATATGTATGCATACAATATAATTTTTTATATATTGTATGCATACATATACTGTTTTATATATGTATGCATACATATATACTTTTTTGTATGTACACATACACATATAATTTTTTATATATGCATGCATACATATATGATTTTTATATAGGCATGCATACATATATAATTTTTTATATATGCATGCATATATAAATGCATATAACTTTTTATATATGTATGCATACATAACTTTTTATATATGTATGCATACATAACTTTTTATATATGTATGCATACATAACTTTTATATATGTATGCATACATAACTTTTTATATATGTATGCATACATATATAACTTTTTATATATGTATGCATACATATATAACTTTT\t*\tSA:Z:chr3,96004627,+,1189S35M26I399M,60,26;\tMD:Z:1153\tRG:Z:GATKSVContigAlignments\tNM:i:58\tAS:i:1079\tXS:i:58";
        AlignmentInterval gapped = fromSAMRecordString(samString, true);
        samString = "asm005554:tig00000\t2048\tchr3\t96004627\t60\t1189H35M26I399M\t*\t00\tTTTTTATATATGTATGCATACATATATAATTTTTATATGTATGCATACATATATAATTTTTTATATGTATGCATACATATATAATTTTTATATGTATGCATACAATATAATTTTTTATATATTGTATGCATACATATACTGTTTTATATATGTATGCATACATATATACTTTTTTGTATGTACACATACACATATAATTTTTTATATATGCATGCATACATATATGATTTTTATATAGGCATGCATACATATATAATTTTTTATATATGCATGCATATATAAATGCATATAACTTTTTATATATGTATGCATACATAACTTTTTATATATGTATGCATACATAACTTTTTATATATGTATGCATACATAACTTTTATATATGTATGCATACATAACTTTTTATATATGTATGCATACATATATAACTTTTTATATATGTATGCATACATATATAACTTTT\t*\tSA:Z:chr3,96003552,+,1137M58I16M438S,60,58;\tMD:Z:434\tRG:Z:GATKSVContigAlignments\tNM:i:26\tAS:i:392\tXS:i:204";
        AlignmentInterval nonGapped = fromSAMRecordString(samString, true);
        ArrayList<AlignmentInterval> splitAlignments = Lists.newArrayList((ContigAlignmentsModifier.splitGappedAlignment(gapped, GAPPED_ALIGNMENT_BREAK_DEFAULT_SENSITIVITY, 1649)));
        data.add(new Object[]{new AlignedContig.GoodAndBadMappings(Arrays.asList(gapped, nonGapped)),
                              new AlignedContig.GoodAndBadMappings(Arrays.asList(splitAlignments.get(0), nonGapped), Collections.singletonList(splitAlignments.get(1)))
        });

        return data.toArray(new Object[data.size()][]);
    }
    @Test(groups = "sv", dataProvider = "forSplitGapsAndDropContainedAln")
    public void testSplitGapsAndDropContainedAln( final AlignedContig.GoodAndBadMappings input, final AlignedContig.GoodAndBadMappings expectedOutput) {
        Assert.assertEquals(AlignedContig.splitGapsAndDropAlignmentContainedByOtherOnRead(input),
                            expectedOutput);
    }

    // functionality group 3.2: after gap split, further reclassify some good mapping as bad

    @DataProvider
    private Object[][] forRemoveNonUniqueMappings() {
        final List<Object[]> data = new ArrayList<>(20);

        // case zero: only one or two alignments
        AlignmentInterval singleAlignment = fromSAMRecordString("asm000146:tig00004\t2048\tchrUn_JTFH01001925v1_decoy\t600\t60\t646M4D370M\t*\t0\t0\tTGACTGAGCAAGTGTGGGAGTGTGAGTGAATGAGTGAGTGAGTGAATGAGTGACTGAGTGTGAGTGAACGAGTGACTGAGCAAGTGTGTGAGTGACTGAGCGAGTGTGTGTGAGTGAATAAGTGAGTGAGTGAATGAATCAGTGACTGTGTGTGATGAGCAAGTGTATGTGAATGAGTGACTGAGCAAGTGAATGAGTGTGAGTGTGTGAGTGACTGTGAGTGAGTGAATGAGCAAGTGTGAGTGAATGAGTGTGTGTGTGAATGAGCGAGTGAGTGAATAAGTGAGTGAATGAGTGTGCGAGTGAGTGAATGATTGTGAGTGAATGAGTGAGTGAGTAAGTGTGAGCGTGTGAGTGAATGAGTGTGTGTGAGTGAATGAGTGGATGAGTGTGAGTGAATGAGTGACTGAGCGAGTATGTGAGTGAATGAGTGACTGTGAGTGAGTGAGCAAGTGTGAGTGAATGAGTGTATGAGTGAATGAATGAGTGAGCGAGTGTGTGTGACTGAATGAGTGTGAGTGTGTGAGTAAATGAGTGTGTGAATGAGTGACTGTGACTGAGTGTGAATGAGTGACAGCAAGTGTGTGAGTGAATGAGTGTGAATATGAGTGAGTGAATGAGTGAGCAAGTGTGTGAGTGAATGAGTGTATGAGTGAATGAATGAGCGAGTGTGTGTGACTGAATGAGTGTGTGTGAGTAAATGAGTGTGAGTGAATGAGTGAGTGACTGTGAGTGTGAATGAGTGACTGAACAACTGTGTGTGAATGAGTGTATGAGTGAATGAATGAATGAGCGTGTGTGTGAGTGACTGAATGAGAGTGTGAGTAAATGAGTGTGAGTGAATGAGTGAGTGACTGAGTGACTGAATGAGTGACTGAGCAAGTGTGTGAGTGAATGAGTGTGAGTATGAGTGAATGAGTGAGCATGTGTGTGAGTGAGTGAGTGGGTGTGAGTGAGTGAATGAGTGACTGAATGTGTGAGTGTGAGTGAATGAGTGACTGAATGTGAGTGTGACT\t*SA:Z:chr1,4066380,-,441S22M2D18M2I138M2I10M4D28M1I24M10D13M4D49M4D51M4D65M2D50M10I34M2I56M,60,59;chr1,4064439,-,33S55M4I58M4D34M832S,20,15;chr1,4064651,-,255S32M2D99M630S,35,15;chr1,4064590,-,368S73M2D23M552S,60,8;\tMD:Z:314G2A0T0T4G3A317^GTGA370\tRG:Z:GATKSVContigAlignments\tNM:i:10\tAS:i:966\tXS:i:259", true);
        data.add(new Object[]{new AlignedContig.GoodAndBadMappings(Collections.singletonList(singleAlignment)), new AlignedContig.GoodAndBadMappings(Collections.singletonList(singleAlignment)),
                AlignedContig.ALIGNMENT_MQ_THRESHOLD, AlignedContig.ALIGNMENT_LOW_READ_UNIQUENESS_THRESHOLD});

        AlignmentInterval head = new AlignmentInterval(new SimpleInterval("chr1:1-100"), 1, 100, TextCigarCodec.decode("100M94S"), true, 60, 0, 100, ContigAlignmentsModifier.AlnModType.NONE);
        AlignmentInterval tail = new AlignmentInterval(new SimpleInterval("chr1:137-200"), 137, 200, TextCigarCodec.decode("130S64M"), true, 60, 0, 64, ContigAlignmentsModifier.AlnModType.NONE);
        data.add(new Object[]{new AlignedContig.GoodAndBadMappings(Arrays.asList(head, tail)),
                              new AlignedContig.GoodAndBadMappings(Arrays.asList(head, tail)),
                              AlignedContig.ALIGNMENT_MQ_THRESHOLD, AlignedContig.ALIGNMENT_LOW_READ_UNIQUENESS_THRESHOLD
        });

        // case one: some alignments are bad MQ
        int middleAlnMQ = 19;
        AlignmentInterval middle = new AlignmentInterval(new SimpleInterval("chr2:1-29"), 102, 130, TextCigarCodec.decode("101S29M64S"), false, middleAlnMQ, 2, 20, ContigAlignmentsModifier.AlnModType.NONE);
        data.add(new Object[]{new AlignedContig.GoodAndBadMappings(Arrays.asList(head, middle, tail)),
                              new AlignedContig.GoodAndBadMappings(Arrays.asList(head, tail), Collections.singletonList(middle)),
                              AlignedContig.ALIGNMENT_MQ_THRESHOLD, AlignedContig.ALIGNMENT_LOW_READ_UNIQUENESS_THRESHOLD
        });
        middleAlnMQ = 20;
        middle = new AlignmentInterval(new SimpleInterval("chr2:1-29"), 102, 130, TextCigarCodec.decode("101S29M64S"), false, middleAlnMQ, 2, 20, ContigAlignmentsModifier.AlnModType.NONE);
        data.add(new Object[]{new AlignedContig.GoodAndBadMappings(Arrays.asList(head, middle, tail)),
                              new AlignedContig.GoodAndBadMappings(Arrays.asList(head, middle, tail)),
                              AlignedContig.ALIGNMENT_MQ_THRESHOLD, AlignedContig.ALIGNMENT_LOW_READ_UNIQUENESS_THRESHOLD
        });

        // case two: some alignments would be too short after overlap removal

        // first, no overlap but still too short
        middleAlnMQ = 40;
        middle = new AlignmentInterval(new SimpleInterval("chr2:1-9"), 122, 130, TextCigarCodec.decode("121S9M64S"), false, middleAlnMQ, 0, 9, ContigAlignmentsModifier.AlnModType.NONE);
        data.add(new Object[]{new AlignedContig.GoodAndBadMappings(Arrays.asList(head, middle, tail)),
                              new AlignedContig.GoodAndBadMappings(Arrays.asList(head, tail), Collections.singletonList(middle)),
                              AlignedContig.ALIGNMENT_MQ_THRESHOLD, AlignedContig.ALIGNMENT_LOW_READ_UNIQUENESS_THRESHOLD
        });
        // or just long enough
        middle = new AlignmentInterval(new SimpleInterval("chr2:1-10"), 121, 130, TextCigarCodec.decode("120S10M64S"), false, middleAlnMQ, 1, 9, ContigAlignmentsModifier.AlnModType.NONE);
        data.add(new Object[]{new AlignedContig.GoodAndBadMappings(Arrays.asList(head, middle, tail)),
                              new AlignedContig.GoodAndBadMappings(Arrays.asList(head, middle, tail)),
                              AlignedContig.ALIGNMENT_MQ_THRESHOLD, AlignedContig.ALIGNMENT_LOW_READ_UNIQUENESS_THRESHOLD
        });

        // now real data
        String samString = "asm000266:tig00003\t0\tchr1\t10817996\t60\t1017S43M2I870M\t*\t0\t0\tCTTCTGCCGCCCAGGCTCCCCTGGGATTCTGCAGCCTCCTCCTTGATGGCTGCTGGCCCTGCCCACCTGCCGTTCTTGCAGTGGCAAACCTGAGCCCACAGTCCCCTGCTCAAAGCCCATCGGAGGCTCCTGGGGCCTGCAGGGCCTGGTCCAGGTCCCTTCACATGACTCGCAAGGTCCCACCACCCTCTCTGGCCTCACCCTCTCCTCTCTTCGCTGGGGCTCCCCCTCTCCAATGCACTGGCCTGCACTCACTTCCCCAGGCCCAGGTGGTCTAGCCCCCACCTTTGCCCCTGCTGTGGCTTCCCAGGGAATGCTCTTCCTACCTGCTCCCTGCCCCCACCCCTCTGTTGTAAGATCTCAAATGAGACAGCACCTTCCTGGCTCCTGCCTCCCTAGCCTTGACCCCCCTGCAAGTTCCCAGAAACTCTGGCTTTTCCTGCGTGTAGGACATCACCTGGTCCCTGTCTTCAGAGAAGGACATGAAGCAAGCCCACTGGTACTGGCACCTTCATTCAGCTCATTCTTCAACCAGCAAGGATTTATTGAGCACATACTATGAACAGCTGCCAGGGCTGAGCCTGGGGTGCTTGCGCCCCTGAGGACTGGGGCCCTCAGACCCAGGGGGTATGGGTGGAAGAAGAACTTGGCTATTTAGAAAGGGACTCTAGGAAGGCACATGTCATCTCCTCTCCTCCAGGCCTGAGAGCATATACAAGGCCAGTACCATGAGCTAATAATATTTTACTTTTCCCCGTAGAGCACAGCATTGGGCTTGGCATACAGTAGGGGCTCAACCAATGCAGGCAGAAGAGAACTGACAGATGATAAGGTTTTCTTTCTTTCTTTCTTTCTCTCTTTCTCCCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCCTTTCCTTTCCTTTCCTTTCCTTTCCTTCCTTTCCTTTCCTTTCCTTTCCTTTCCCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTTTTGAGACAGAGTCTTGCTCTGCCGCCCAGGCTGGAGTGCAGTGGTGTGATCTCGGTTCACTGCAACCTCCGCCTCCCGGGTTCAAGTGATTCTTGCACCTTAGCCTCCCAAGTAGCTGGGATTACAGTTGCCCCCAACCATGCCTGGCTAATTTTTGTATTTTTAGTAGAGGCAGGGTTTCACCATGTTGGCCAGGCTGGTCTCAAACTCCTGACCTCAGATGATCCACCCGCCTCGGCCTCCAAAAGTGCTGGGATTATAGGCATGAGCCACCGTGCCTGGCCAAAGATAACCCTACACCAGGAACTTCATGAGTTCCAGGAGGGAAAGGCAGACTAGTGTGTGTTGCAGCAGGCAGGGAGGGCTTCCTGAGGGAGGTGCTGAGAACAGGGCCTTGAAGCCCGTGGAGGCTCAAAGTAGTTGGGAGGGAGGAGGGCGGATGCTTTCTAGGGATTGTGGAGACCAGGATACAGGCAACAGGAGCAAGAGGCGTGAGGTTGAAAGCAGGTGGGAGGGGTGGGGCATCTGTACAAACATCGTGGGTGATGTTTAGGAGAGTGCCAGGCTGTGCCTCTGGCCACCACCATACCTAAGACCCCTAAGTCTTGCTCTGGCTGGGGGTGACTGCGGGCCACAGTTCTTGTCTGCAGGGAAACCAATGGCTGCAGTTAAAGACAAGGCTGCCCTCCCCCCAAGCTCCAGAGACTGGGGAGTGCCCCGGGCAGGGCTTGCCCAGACCTGGCACTCCAGCTGCACCCTCCGCCCTGGGACATCTTGTACCCAGGAGGACCTATTAAAGGGACAAAGGTCCCCATGGGGTGCAGGCACCCCAGGCTCAGCCCTGGCAGCTGGCCCGGGCTTG\t*\tSA:Z:chr1,10817090,+,854M1078S,60,3,843;chr22,22585882,+,945S125M862S,20,0,125;chr8,6150682,-,1005S91M836S,0,3,76;chr11,48640941,+,906S55M971S,60,0,55;\tMD:Z:913\tRG:Z:GATKSVContigAlignments\tNM:i:2\tAS:i:895\tXS:i:134";
        AlignedContig alignedContig = fromPrimarySAMRecordString(samString, true);
        AlignedContig.GoodAndBadMappings expected = new AlignedContig.GoodAndBadMappings(
                Arrays.asList(fromSAMRecordString("asm000266:tig00003\t2048\tchr1\t10817090\t60\t854M1078S\t*\t0\t0\tCTTCTGCCGCCCAGGCTCCCCTGGGATTCTGCAGCCTCCTCCTTGATGGCTGCTGGCCCTGCCCACCTGCCGTTCTTGCAGTGGCAAACCTGAGCCCACAGTCCCCTGCTCAAAGCCCATCGGAGGCTCCTGGGGCCTGCAGGGCCTGGTCCAGGTCCCTTCACATGACTCGCAAGGTCCCACCACCCTCTCTGGCCTCACCCTCTCCTCTCTTCGCTGGGGCTCCCCCTCTCCAATGCACTGGCCTGCACTCACTTCCCCAGGCCCAGGTGGTCTAGCCCCCACCTTTGCCCCTGCTGTGGCTTCCCAGGGAATGCTCTTCCTACCTGCTCCCTGCCCCCACCCCTCTGTTGTAAGATCTCAAATGAGACAGCACCTTCCTGGCTCCTGCCTCCCTAGCCTTGACCCCCCTGCAAGTTCCCAGAAACTCTGGCTTTTCCTGCGTGTAGGACATCACCTGGTCCCTGTCTTCAGAGAAGGACATGAAGCAAGCCCACTGGTACTGGCACCTTCATTCAGCTCATTCTTCAACCAGCAAGGATTTATTGAGCACATACTATGAACAGCTGCCAGGGCTGAGCCTGGGGTGCTTGCGCCCCTGAGGACTGGGGCCCTCAGACCCAGGGGGTATGGGTGGAAGAAGAACTTGGCTATTTAGAAAGGGACTCTAGGAAGGCACATGTCATCTCCTCTCCTCCAGGCCTGAGAGCATATACAAGGCCAGTACCATGAGCTAATAATATTTTACTTTTCCCCGTAGAGCACAGCATTGGGCTTGGCATACAGTAGGGGCTCAACCAATGCAGGCAGAAGAGAACTGACAGATGATAAGGTTTTCTTTCTTTCTTTCTTTC\t*\tSA:Z:chr1,10817996,+,1017S43M2I870M,60,2;chr22,22585882,+,945S125M862S,20,0;chr8,6150682,-,1005S91M836S,0,3;chr11,48640941,+,906S55M971S,60,0;\tMD:Z:0A356G251A244\tRG:Z:GATKSVContigAlignments\tNM:i:3\tAS:i:843\tXS:i:0", true),
                              fromSAMRecordString("asm000266:tig00003\t2048\tchr22\t22585882\t20\t945S125M862S\t*\t0\t0\tCTTTCCTTTCCTTTCCCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTT\t*\tSA:Z:chr1,10817996,+,1017S43M2I870M,60,2;chr1,10817090,+,854M1078S,60,3;chr8,6150682,-,1005S91M836S,0,3;chr11,48640941,+,906S55M971S,60,0;\tMD:Z:125\tRG:Z:GATKSVContigAlignments\tNM:i:0\tAS:i:125\tXS:i:111", true),
                              fromSAMRecordString("asm000266:tig00003\t0\tchr1\t10817996\t60\t1017S43M2I870M\t*\t0\t0\tCTTCTGCCGCCCAGGCTCCCCTGGGATTCTGCAGCCTCCTCCTTGATGGCTGCTGGCCCTGCCCACCTGCCGTTCTTGCAGTGGCAAACCTGAGCCCACAGTCCCCTGCTCAAAGCCCATCGGAGGCTCCTGGGGCCTGCAGGGCCTGGTCCAGGTCCCTTCACATGACTCGCAAGGTCCCACCACCCTCTCTGGCCTCACCCTCTCCTCTCTTCGCTGGGGCTCCCCCTCTCCAATGCACTGGCCTGCACTCACTTCCCCAGGCCCAGGTGGTCTAGCCCCCACCTTTGCCCCTGCTGTGGCTTCCCAGGGAATGCTCTTCCTACCTGCTCCCTGCCCCCACCCCTCTGTTGTAAGATCTCAAATGAGACAGCACCTTCCTGGCTCCTGCCTCCCTAGCCTTGACCCCCCTGCAAGTTCCCAGAAACTCTGGCTTTTCCTGCGTGTAGGACATCACCTGGTCCCTGTCTTCAGAGAAGGACATGAAGCAAGCCCACTGGTACTGGCACCTTCATTCAGCTCATTCTTCAACCAGCAAGGATTTATTGAGCACATACTATGAACAGCTGCCAGGGCTGAGCCTGGGGTGCTTGCGCCCCTGAGGACTGGGGCCCTCAGACCCAGGGGGTATGGGTGGAAGAAGAACTTGGCTATTTAGAAAGGGACTCTAGGAAGGCACATGTCATCTCCTCTCCTCCAGGCCTGAGAGCATATACAAGGCCAGTACCATGAGCTAATAATATTTTACTTTTCCCCGTAGAGCACAGCATTGGGCTTGGCATACAGTAGGGGCTCAACCAATGCAGGCAGAAGAGAACTGACAGATGATAAGGTTTTCTTTCTTTCTTTCTTTCTCTCTTTCTCCCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCCTTTCCTTTCCTTTCCTTTCCTTTCCTTCCTTTCCTTTCCTTTCCTTTCCTTTCCCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTCTTTTTTGAGACAGAGTCTTGCTCTGCCGCCCAGGCTGGAGTGCAGTGGTGTGATCTCGGTTCACTGCAACCTCCGCCTCCCGGGTTCAAGTGATTCTTGCACCTTAGCCTCCCAAGTAGCTGGGATTACAGTTGCCCCCAACCATGCCTGGCTAATTTTTGTATTTTTAGTAGAGGCAGGGTTTCACCATGTTGGCCAGGCTGGTCTCAAACTCCTGACCTCAGATGATCCACCCGCCTCGGCCTCCAAAAGTGCTGGGATTATAGGCATGAGCCACCGTGCCTGGCCAAAGATAACCCTACACCAGGAACTTCATGAGTTCCAGGAGGGAAAGGCAGACTAGTGTGTGTTGCAGCAGGCAGGGAGGGCTTCCTGAGGGAGGTGCTGAGAACAGGGCCTTGAAGCCCGTGGAGGCTCAAAGTAGTTGGGAGGGAGGAGGGCGGATGCTTTCTAGGGATTGTGGAGACCAGGATACAGGCAACAGGAGCAAGAGGCGTGAGGTTGAAAGCAGGTGGGAGGGGTGGGGCATCTGTACAAACATCGTGGGTGATGTTTAGGAGAGTGCCAGGCTGTGCCTCTGGCCACCACCATACCTAAGACCCCTAAGTCTTGCTCTGGCTGGGGGTGACTGCGGGCCACAGTTCTTGTCTGCAGGGAAACCAATGGCTGCAGTTAAAGACAAGGCTGCCCTCCCCCCAAGCTCCAGAGACTGGGGAGTGCCCCGGGCAGGGCTTGCCCAGACCTGGCACTCCAGCTGCACCCTCCGCCCTGGGACATCTTGTACCCAGGAGGACCTATTAAAGGGACAAAGGTCCCCATGGGGTGCAGGCACCCCAGGCTCAGCCCTGGCAGCTGGCCCGGGCTTG\t*\tSA:Z:chr1,10817090,+,854M1078S,60,3;chr22,22585882,+,945S125M862S,20,0;chr8,6150682,-,1005S91M836S,0,3;chr11,48640941,+,906S55M971S,60,0;\tMD:Z:913\tRG:Z:GATKSVContigAlignments\tNM:i:2\tAS:i:895\tXS:i:134", true)),
                Arrays.asList(fromSAMRecordString("asm000266:tig00003\t2064\tchr8\t6150682\t0\t1005S91M836S\t*\t0\t0\tGGAAAGGAAAGGAAAGGAAAGGAAAGAAAGAAAGAAAGAAAGAAAGAAAGAAAGAAAGAAAGGGAGAAAGAGAGAAAGAAAGAAAGAAAGA\t*\tSA:Z:chr1,10817996,+,1017S43M2I870M,60,2;chr1,10817090,+,854M1078S,60,3;chr22,22585882,+,945S125M862S,20,0;chr11,48640941,+,906S55M971S,60,0;\tMD:Z:62A0A7A19\tRG:Z:GATKSVContigAlignments\tNM:i:3\tAS:i:76\tXS:i:72", true),
                              fromSAMRecordString("asm000266:tig00003\t2048\tchr11\t48640941\t60\t906S55M971S\t*\t0\t0\tCTTTCCTTTCCTTTCCTTTCCTTTCCTTCCTTTCCTTTCCTTTCCTTTCCTTTCC\t*\tSA:Z:chr1,10817996,+,1017S43M2I870M,60,2;chr1,10817090,+,854M1078S,60,3;chr22,22585882,+,945S125M862S,20,0;chr8,6150682,-,1005S91M836S,0,3;\tMD:Z:55\tRG:Z:GATKSVContigAlignments\tNM:i:0\tAS:i:55\tXS:i:0", true))
        );
        data.add(new Object[]{new AlignedContig.GoodAndBadMappings(alignedContig.getAlignments(), Collections.emptyList()),
                              expected,
                              20, 40
        });

        return data.toArray(new Object[data.size()][]);
    }
    @Test(groups = "sv", dataProvider = "forRemoveNonUniqueMappings")
    public void testRemoveNonUniqueMappings( final AlignedContig.GoodAndBadMappings input, final AlignedContig.GoodAndBadMappings expectedOutput,
                                             final int mapQThresholdInclusive, final int uniqReadLenInclusive) {
        AlignedContig.GoodAndBadMappings actual = AlignedContig.removeNonUniqueMappings(input, mapQThresholdInclusive, uniqReadLenInclusive);
        Assert.assertEquals(actual, expectedOutput);
    }

    // misc

    @DataProvider
    private Object[][] forConfigurationSorting() {
        final List<Object[]> data = new ArrayList<>(20);
        // case for two equally-good configurations, one has fewer alignments
        String sam = "asm001160:tig00000\t16\tchr1\t93876139\t60\t516S1317M\t*\t0\t0\tCATGTTGCCCAAGCCAGTCTTGAACTCCAGGGCTCAAAATGCTGAAATTACAGGCACGAGTCACTTACTGCTCTTAACAATCACGTACAAAAATCTTAACATATGATTTTTTTTTTTTTTTTTTGAGACAACATCTCCCTCCATTGCCCAGGCTGGAGTGCAGCGGCACAATCATGGCTCACCGCAGCCTCAATGTCCAGGGCTCAAGCAATCCTCCCACCTCAGCTTCCCAAGTAGCTGGGACCACAGGCGCACAGGGCACGGCTAATTTAAAAAAAATTTTTTGTGTAGAGATAGGGTCTCCTTATATTGCCCAGGCTGATCTCAAACACCTACTTGGGCTCAAGTGATCCTCCTGCCTCAGCCTCACAAAGTGCTGGGATTACAGGCATGAGTCACTGCATCCAACAGATTGATTTCTAATATGTCACCAAAAGGAGCACCTTTAGCTATGATTGGTGGGAAAAATATGACTAAAATAGGTATCCAAAAAGACAAGGGAAATGCTGGATAGAAGAGCCATTCCATGAAGAACCCAAGGCAGTGATTTTCTCATTCCCCAGGCTAACATTTCATATTTTTATGGTAAATTAACCACTTGAAATACATGTATCAAAAACTTATAAAAATAAAGGAAAAACTTACAGTTTAGCCTTTGTGCTATTTAGGAAGTCTTCTTCATCACTAAACTCATCTTCATTTTCGTCATGGTCTGATGAATCTTCTTCACTTTTTTCATCCTCTTCCTCTTCTTTTTCTTCCTCAATGGCAACCTCACTTGCCTTGTCTTCCTCTTCCAAGTAAAAATTTTTATCAGCACTCATTCCAGGAGTTGTGTCAATTACAAACAATGCATTGTCACATGACAGACTTCCTGTGTCTCCACTTAATGACTCCCTTTGGCCACTATTTTCAACAAAACATAAAGTATCCTCTTCATTCTCACTGTTTTCAGACTGTTGGCTTTCATCACTGCTGAGAACTAGTAAGACAGAATTATCTTTACCCTGAGATGTGTTGGGCGCAGACGTGTATAGTTTGGTATCACATTCAAAATCTACATTCCCTTCACTGTTCATGTCTTCACTGACACTTATAACTGTGGACTCTTCTTCATCATCACTACCACCACAATCACCAAACTTTGTCAAGTCACTTGCTTTTATGGGGCTCTTTTTGTTGTTATTCCATCTGCCTACTTCCACAGTTGCAAATGTTTGAGTTAATGATTTCATTACAGCCTCAGAGTTCAGATTAGAGTGCACTGATACAGCATTTTTATTTTGGGGGGTTGAATGTCTCTGAGAAACTAACTGTTGAAGGCTAGTGTCCTGAAGTTCAGAAAGATTCTTCAGCTGAGAACTTTTCTCATTAATTTCTTTCCCCTCATCTGTTATTCCATTGGCATCTTCATCCAAATCCTTACAATTCTGTTTTGTTTCTTTAAGAGATTCAACATTGGCCTGTTCGTGCACTGTTAATATATTTTCTGAACTTCTGTGGGAGAAATCATCATCAAAGTCATTATTATAGAAATTTGGCTTATTTATCTCAGAAAGAGATCTTGCTTGTAAATGGGAAGTTTGTCTGGTATCTGAATCCTCTGAATTCACAGGTGTACCCACGATCTGTTTCTCATTTCCTGGTACAATCTTACTATCTTTCTTTTCAGTTTGTGCCTTTAATTTCCTCTGCATACTCCTGGTTCTTCTAGTTGCAATTCCAGAGAATGAAATGTCTGAGCTTGATGTCTCAGCATCAGATATAGCTTCTGTATGAGATTCTTGGCTTGGATCTGTCAGAGATTTAGCCTTACTTCTTCTGGCTCCTGTA\t*\tSA:Z:chr1,61662787,-,165H74M1594H,12,2,64;chr15,43085606,+,1427H65M341H,0,2,55;chr10,96642377,+,1660H67M106H,60,5,42;chrUn_JTFH01001621v1_decoy,641,-,106M2I408M1317H,60,5,481;\tMD:Z:1317\tRG:Z:GATKSVContigAlignments\tNM:i:0\tAS:i:1317\tXS:i:0";
        AlignedContig alignedContig = fromPrimarySAMRecordString(sam, true);

        AssemblyContigWithFineTunedAlignments tigOne = new AssemblyContigWithFineTunedAlignments(
                new AlignedContig(alignedContig.getContigName(), alignedContig.getContigSequence(),
                        Arrays.asList(alignedContig.getAlignments().get(0), alignedContig.getAlignments().get(1))),
                alignedContig.getAlignments().subList(2, alignedContig.getAlignments().size()).stream().map(AlignmentInterval::toPackedString).collect(Collectors.toList()),
                true,
                (AlignmentInterval) null
        );
        AssemblyContigWithFineTunedAlignments tigTwo = new AssemblyContigWithFineTunedAlignments(
                new AlignedContig(alignedContig.getContigName(), alignedContig.getContigSequence(),
                        Arrays.asList(alignedContig.getAlignments().get(0), alignedContig.getAlignments().get(1), alignedContig.getAlignments().get(4))),
                alignedContig.getAlignments().subList(2, 4).stream().map(AlignmentInterval::toPackedString).collect(Collectors.toList()),
                true,
                (AlignmentInterval) null);

        data.add(new Object[]{Arrays.asList(tigTwo, tigOne), Arrays.asList(tigOne, tigTwo)});

        // case for two equally-good configurations, having same num of alignments, one has lower total NM
        sam = "asm000168:tig00027\t0\tchr1\t4939534\t60\t54S139M96S\t*\t0\t0\tGTCCTCCGTATGACGTCAGTGTCCTCCATATGACATCAATATCCTCCATATGACATCAATATCCTCCATATGATGTCAGTGTGCTCCATATGACATCAATATCCTCCATATGATGTCAATATCCTGCGTATGATGTCAATATCCTCCGTATGATGTCAATATCCTCCATATGATGTCAATATCCTCTGTATGATGTCAGTGTCCTCCATATGATGTCAATCGCCTCCATATGATGCCAATATCCTCCGTATGATGTCAATGCCCTCCGTATGATGTCAATGTCCTCCGT\t*\tSA:Z:chr1,4939535,+,155H134M,60,15,61;chr1,4939436,+,66M223H,23,3,51;chrUn_JTFH01000538v1_decoy,1338,-,153M136H,60,1,148;\tMD:Z:14A13C10T0G5G24C52G1G12\tRG:Z:GATKSVContigAlignments\tNM:i:8\tAS:i:99\tXS:i:45";
        alignedContig = fromPrimarySAMRecordString(sam, true);

        tigOne = new AssemblyContigWithFineTunedAlignments(
                new AlignedContig(alignedContig.getContigName(), alignedContig.getContigSequence(),
                        Arrays.asList(alignedContig.getAlignments().get(0), alignedContig.getAlignments().get(1), new AlignmentInterval("chrUn_JTFH01000538v1_decoy,1338,-,153M136H,60,1,148"))),
                Collections.singletonList(new AlignmentInterval("chr1,4939535,+,155H134M,60,15,61").toPackedString()),
                true,
                (AlignmentInterval)null
        );
        tigTwo = new AssemblyContigWithFineTunedAlignments(
                new AlignedContig(alignedContig.getContigName(), alignedContig.getContigSequence(),
                        Arrays.asList(alignedContig.getAlignments().get(0), alignedContig.getAlignments().get(1), new AlignmentInterval("chr1,4939535,+,155H134M,60,15,61"))),
                Collections.singletonList(new AlignmentInterval("chrUn_JTFH01000538v1_decoy,1338,-,153M136H,60,1,148").toPackedString()),
                true,
                (AlignmentInterval)null);
        data.add(new Object[]{Arrays.asList(tigTwo, tigOne), Arrays.asList(tigOne, tigTwo)});


        return data.toArray(new Object[data.size()][]);
    }
    @Test(groups = "sv", dataProvider = "forConfigurationSorting")
    public void testConfigurationSorting(final List<AssemblyContigWithFineTunedAlignments> tobeSorted,
                                         final List<AssemblyContigWithFineTunedAlignments> expectedRepresentationsInOrder) {

        tobeSorted.sort(AlignedContig.getConfigurationComparator());
        Assert.assertEquals(tobeSorted, expectedRepresentationsInOrder);
    }

    @DataProvider Object[][] forSimpleChimeraWithStichableAlignments() {
        final List<Object[]> data = new ArrayList<>(20);

        AlignmentInterval zero = fromSAMRecordString("asm019085:tig00005\t2064\tchr8\t38525855\t0\t525H36M7D70M\t*\t0\t0\tCCTTCCCTTCCCTTCCCCTTCCTTCCTTTCTTCCTTCCTTCCCTTCCCTTCCCCTTCCTTCCTTCCTTCCTTCCTTCCTTCCTTCCTTCCTTCCTTCCTTCCTTCC\t*\tSA:Z:chr20,43927117,-,374M257S,60,3;chr20,43927479,-,342S74M7D69M146S,60,8;chr20,43927560,-,416S51M15D58M5I37M64S,60,20;\tMD:Z:28C7^TCCCTTC70\tRG:Z:GATKSVContigAlignments\tNM:i:8\tAS:i:78\tXS:i:75", true);
        AlignmentInterval one = fromSAMRecordString("asm019085:tig00005\t2064\tchr20\t43927560\t60\t416H51M15D58M5I37M64H\t*\t0\t0\tTCTTGGCTTTGCCACCTATGAAGAGTTAATTTCCTTCCTTCCTTCCTTCCTTCCTTCCTTCCTTCCTCCCCTTCCCTTCCCTTCCCCTTCCTTCCTTCCTTTCTTCCTTCCTTCCCTTCCCTTCCCCTTCCTTCCTTTCTTCCTTCCTTCC\t*\tSA:Z:chr20,43927117,-,374M257S,60,3;chr20,43927479,-,342S74M7D69M146S,60,8;chr8,38525855,-,525S36M7D70M,0,8;\tMD:Z:51^TCCTTCCTTCCTTCC95\tRG:Z:GATKSVContigAlignments\tNM:i:20\tAS:i:94\tXS:i:46", true);
        AlignmentInterval two = fromSAMRecordString("asm019085:tig00005\t2064\tchr20\t43927479\t60\t342H74M7D69M146H\t*\t0\t0\tACACACACACACACACACACACACACACACACGTCATGAGATATGCTTTGGAGTCATACTGGCCTGGGATCAAATCTTGGCTTTGCCACCTATGAAGAGTTAATTTCCTTCCTTCCTTCCTTCCTTCCTTCCTTCCTTCCTCC\t*\tSA:Z:chr20,43927117,-,374M257S,60,3;chr20,43927560,-,416S51M15D58M5I37M64S,60,20;chr8,38525855,-,525S36M7D70M,0,8;\tMD:Z:32A41^TCTTGGC69\tRG:Z:GATKSVContigAlignments\tNM:i:8\tAS:i:115\tXS:i:0", true);
        AlignmentInterval three = fromSAMRecordString("asm019085:tig00005\t16\tchr20\t43927117\t60\t374M257S\t*\t0\t0\tCCAAATTATGGCTTCTTTTTCAGATATTTATAACTTATTTATAGTTCCTCCCAGTTCAAATGCAACTTACCAATCAGGGGTCATTTTTACCATAAGCAGAGTTGCCTGGTAACATAGTTAACGTCCCACTTTCCTCAGGTTTCCAGGGGAGTTATGCTCCGCAATTAACAAAGGTGAAATTCTCTTGCAACAAGGAAAAGGGTTTGGTTAACCCTTTCCCCCATATTCATCATCCTACTTTTTTCCCCTGTGGGCTGGTATTTTTGGCATCTCTTTTGGAAGGATGAATGGAGTCCTCAGTAATATATTCCACACTGTGTACATTTTCTGATGATCTATGTAACACACACACACACACACACACACACACACACGTCATGAGATATGCTTTGGAGTCATACTGGCCTGGGATCAAATCTTGGCTTTGCCACCTATGAAGAGTTAATTTCCTTCCTTCCTTCCTTCCTTCCTTCCTTCCTTCCTCCCCTTCCCTTCCCTTCCCCTTCCTTCCTTCCTTTCTTCCTTCCTTCCCTTCCCTTCCCCTTCCTTCCTTTCTTCCTTCCTTCCCTTCCCTTCCCCTTCCTTCCTTCCTTCCTTCCTTCCTTCCTTCCTTCCTTCCTTCCTTCCTTCC\t*\tSA:Z:chr20,43927479,-,342S74M7D69M146S,60,8;chr20,43927560,-,416S51M15D58M5I37M64S,60,20;chr8,38525855,-,525S36M7D70M,0,8;\tMD:Z:284C15A38A34\tRG:Z:GATKSVContigAlignments\tNM:i:3\tAS:i:359\tXS:i:0", true);
        data.add(new Object[]{zero, one, false}); // different chr
        data.add(new Object[]{one, three, false});
        data.add(new Object[]{two, three, false});

        data.add(new Object[]{new AlignmentInterval(new SimpleInterval("chr1:1-100"), 1, 100, TextCigarCodec.decode("100M50S"), true, 60, 0, 100, ContigAlignmentsModifier.AlnModType.NONE),
                              new AlignmentInterval(new SimpleInterval("chr1:21-70"), 101, 150, TextCigarCodec.decode("100S50M"), true, 60, 0, 50, ContigAlignmentsModifier.AlnModType.NONE),
                              false
        }); // contains on ref
        data.add(new Object[]{new AlignmentInterval(new SimpleInterval("chr1:1-100"), 1, 100, TextCigarCodec.decode("100M50S"), true, 60, 0, 100, ContigAlignmentsModifier.AlnModType.NONE),
                              new AlignmentInterval(new SimpleInterval("chr1:101-150"), 51, 100, TextCigarCodec.decode("50S50M50S"), true, 60, 0, 50, ContigAlignmentsModifier.AlnModType.NONE),
                              false
        }); // contains on read

        data.add(new Object[]{new AlignmentInterval(new SimpleInterval("chr1:1-100"), 1, 100, TextCigarCodec.decode("100M50S"), true, 60, 0, 100, ContigAlignmentsModifier.AlnModType.NONE),
                              new AlignmentInterval(new SimpleInterval("chr1:101-150"), 101, 150, TextCigarCodec.decode("100S50M"), true, 60, 0, 50, ContigAlignmentsModifier.AlnModType.NONE),
                              true
        });
        data.add(new Object[]{one, two, true});

        // strand switch
        data.add(new Object[]{new AlignmentInterval(new SimpleInterval("chr1:1001001-1001100"), 1, 100, TextCigarCodec.decode("100M1080S"), true, 60, 0, 100, ContigAlignmentsModifier.AlnModType.NONE),
                              new AlignmentInterval(new SimpleInterval("chr1:1000001-1001100"), 81, 1180, TextCigarCodec.decode("1100M80S"), false, 60, 0, 1100, ContigAlignmentsModifier.AlnModType.NONE),
                              false
        });

        return data.toArray(new Object[data.size()][]);
    }
    @Test(groups = "sv", dataProvider = "forSimpleChimeraWithStichableAlignments")
    public void testSimpleChimeraWithStichableAlignments(final AlignmentInterval one, final AlignmentInterval two,
                                                         final boolean expected) {
        Assert.assertEquals(AlignedContig.simpleChimeraWithStichableAlignments(one, two), expected);
    }
    @Test(groups = "sv", expectedExceptions = IllegalArgumentException.class)
    public void testSimpleChimeraWithStichableAlignmentsUnsortedInputs() {
        AlignmentInterval one = fromSAMRecordString("asm019085:tig00005\t2064\tchr20\t43927560\t60\t416H51M15D58M5I37M64H\t*\t0\t0\tTCTTGGCTTTGCCACCTATGAAGAGTTAATTTCCTTCCTTCCTTCCTTCCTTCCTTCCTTCCTTCCTCCCCTTCCCTTCCCTTCCCCTTCCTTCCTTCCTTTCTTCCTTCCTTCCCTTCCCTTCCCCTTCCTTCCTTTCTTCCTTCCTTCC\t*\tSA:Z:chr20,43927117,-,374M257S,60,3;chr20,43927479,-,342S74M7D69M146S,60,8;chr8,38525855,-,525S36M7D70M,0,8;\tMD:Z:51^TCCTTCCTTCCTTCC95\tRG:Z:GATKSVContigAlignments\tNM:i:20\tAS:i:94\tXS:i:46", true);
        AlignmentInterval two = fromSAMRecordString("asm019085:tig00005\t2064\tchr20\t43927479\t60\t342H74M7D69M146H\t*\t0\t0\tACACACACACACACACACACACACACACACACGTCATGAGATATGCTTTGGAGTCATACTGGCCTGGGATCAAATCTTGGCTTTGCCACCTATGAAGAGTTAATTTCCTTCCTTCCTTCCTTCCTTCCTTCCTTCCTTCCTCC\t*\tSA:Z:chr20,43927117,-,374M257S,60,3;chr20,43927560,-,416S51M15D58M5I37M64S,60,20;chr8,38525855,-,525S36M7D70M,0,8;\tMD:Z:32A41^TCTTGGC69\tRG:Z:GATKSVContigAlignments\tNM:i:8\tAS:i:115\tXS:i:0", true);
        AlignedContig.simpleChimeraWithStichableAlignments(two, one);
    }

    @DataProvider
    private Object[][] forSortingAfterGapSplit() {
        final List<Object[]> data = new ArrayList<>(20);

        AlignmentInterval one = TestUtilsForAssemblyBasedSVDiscovery.fromSAMRecordString("asm017280:tig00000\t0\tchr18\t77371711\t60\t1447M50D184M980S\t*\t0\t0\tAATCAGATTTTCAGAACAGAGCTTCTTAGGGAGATGGTTGGACCACCATAGTCCTGCATCTTCAGGGAGCTGCTCACCATCCCCACTACTGCAGGCACCACTGATCCCTGAGTGAGACCCAGGCACTAACAAGACATGAAGAAGGTGCTGGGTTCCTGGCCGGTCCTTTTGACCCATAAGTGTGGTTCAGTTCTTGGCACTGATGGGCTGGTCGGATGGTGGTGAGGTCAAGCACTCCTTCAGTGCCTGAGGCGTGGCGAGTGGACTTTGGGACCTCTTCCTGAGATGATGATTGCTTCTCAAGGGTGGGAATGAGTTCTTCCAGGGGTCAGCGGTGTGTCATAGGCAAGCACAGCACATTACTAGAAAGCATACGTTTTATTAAGTAGAAGGTATCTTTAATTCCCTGAATAGTAAGACATCTTTGGATTGATGGAAAGGTTGCCCTGGGAAAGGTGGAATATTTGTTGTCATGACCTAAAGCAGCATGCCTAACACCATGTTTGCCAGAGCTCATGCCCACACTGCCATCTCAGGGTTTTCTAAGGGAGAGGAAGCAAGAGGAGGCCCTCGAATTTCTAACCGATTGCTGCAGTCAAAAAATTGCTAAGAAGGACTCGGTGGAAAAGGGAAAACTCAAAAGTGAAGTATGCTGTGCAGAAGCCAGAGCTCCACCGGCTCCCAGGATTACAGTAACAGTCAGGCTCCTGCTGTCTGGGCCCAATAACGCCATGCGCAGCATCCACATCAGCTGTCCTGAGAACCAGCAAGGCGTCCAAGGCTTAGTGTGTGTTAGAGGCCAATACTGTCCCTGCTGTTCATTCTCAGGCTAAACCCACGATCTTAGTAAGCACAGATGCAGAGACACAGTTTGTGGTATTGCCGTCATTTTCTTGGGAATGTATCAACAGAGTATACAAGGGTAACAGTTCTTTCTATAAAAAAAATCAACATTTATTTTTATTCATTAATTTACCATGAAAATGATTTTTTCCCAGTAGAGAAGTCAGTCTGGGAAAACCAGATAAAAACTAGAAATGATCTTGTTCGCTGTTGTGGAGGCGGTGCAGGAGGCCCGGGAGCTGGATCACTTGACTTTGTTTTCTCTCCTCCTCTGCTTGCAGAACATCTCTGCTACAGTGGAGGACCCACACTCATAGTTCAGCTCCCCACCTCACACCCCAGCCAAGCCCAGGACCCACACTCATGGCCCCGCTCCCCACCTCACACCCCAGCCAAGCCCAGGACCCACGCTCATGGCCCCGCTCTCCACCTCATACCCCAACCAAGTCTAGGACCCACGCTCATGGCCCTGCTCCCCACCTCACACCCCGACCAAGTCCAGGACCCACGCTCAGGACCCCTCTCCCCACCTCACACCTCAGCCACGCCCAGGACCCATGCTCACAGTTCCGCTCCCCACCTCACACCCCAACCAAGTCCAGGACCCATGCTCATGGCCCTGCTCCCCACCTCACACCCCGACCAAGTCCAGGACCCATGCTCATGGCCCCACTCTCCACCTCATACCCCAGCCAAGCCCAGGACCCACACTCACGGTTCCGCTCCCCACCTCACACCCCGACCAAGCCCAGGACCCACACTCAGGACCCCTCTCCCCACCTCACACCGCAGTTGCAGCATCTGAGGGGAGGGGCAGGACAAGGTGGCAGAGGTGGGGCATTTTTACCCAGAGAAGACCAAAAACAGTCAAAGGGATGGTTTAAATACCTGTCTTTTCTATTTTTTCACTGACTCCATGGTAACAGATTTTGCAGTAAGAACCAACATACACACATCCCTAAAACATATTTCATCAAATAATGCTGACATTATGTGTAGCACATGCTGATATTTTCTATTCTATTCTCATTTTCTCTCTCTTTTTTTCAAACAAATGATAATGAAAATCCACTAAGCTAAGGGTCATGACCAACTAACGGGTAACACCCTTCAGTTTGGAAATACTGATTTAAATTTTGGCTTGCTCTAAGCTATAATTCACATTTGACATTTAGATCATATTTTTCCCCTACTGACTCTCAAGTGGGAGCTGAGAGGCAGTACCTAATAAATACAGAGAAAAAGAAAGAAGGAAGGAAAATTGATATTTACAACACAACCAAGTTTTAATGTGAGTTAAAAAAACGGGTTTCTTTTTGATGGAAATCTTCTAAAATTGTGCTGATGGTTACACACTTCTGTGAATGCACCAAAAACCATTGAATTGTACATTTTAAATGAGGTCATTGTTGCTCTGTGAGTTATATGTCAACAAAGCCGTGAAAATCAGCAAACGAGAACAAACAGTTCTGGCCTAGGCCCTGCGAGGCCTCTTTCTAAGGCCGCCGCACGCCCATCTGGGCCATTAAACTCCTCAAGGCGCTCTGACTTCCTTGTGTAAGAGATATTGCTGTTTCTTATAAGGCACTGATGTACTTTTGCTTCCTTATTGGGGTAAAATTTAAAGACCATAACACTGATGGTTTTAAGAGTGCAGTTAAAAAAAGTTTTGACAAATGTATAAACACATGTAACAACCGCCCCAATCAATACACAGCACATTTCCACGGCCCCAGCAGGTTTCCTTGGTCCCCTTCCCAGCAAATCC\t*\tSA:Z:chr18,77373176,+,1165S338M50I1058M,60,93,1115;\tMD:Z:188G345G84A55T34A16T324A1G0A9C186A15C8C6G5C1C9A24T5A8T14T1G4G16C6A12C0A4T0G1C0C1T19G13^CCCACGCTCAGGACCCCTCTCCCCACCTCACACCTCAGCCACGCCCAGGG10C2T0T1C19A147\tRG:Z:GATKSVContigAlignments\tNM:i:88\tAS:i:1375\tXS:i:262", true);
        AlignmentInterval two = TestUtilsForAssemblyBasedSVDiscovery.fromSAMRecordString("asm017280:tig00000\t2048\tchr18\t77373176\t60\t1165H338M50I1058M\t*\t0\t0\tCTCCCCACCTCACACCCCAGCCAAGCCCAGGACCCACACTCATGGCCCCGCTCCCCACCTCACACCCCAGCCAAGCCCAGGACCCACGCTCATGGCCCCGCTCTCCACCTCATACCCCAACCAAGTCTAGGACCCACGCTCATGGCCCTGCTCCCCACCTCACACCCCGACCAAGTCCAGGACCCACGCTCAGGACCCCTCTCCCCACCTCACACCTCAGCCACGCCCAGGACCCATGCTCACAGTTCCGCTCCCCACCTCACACCCCAACCAAGTCCAGGACCCATGCTCATGGCCCTGCTCCCCACCTCACACCCCGACCAAGTCCAGGACCCATGCTCATGGCCCCACTCTCCACCTCATACCCCAGCCAAGCCCAGGACCCACACTCACGGTTCCGCTCCCCACCTCACACCCCGACCAAGCCCAGGACCCACACTCAGGACCCCTCTCCCCACCTCACACCGCAGTTGCAGCATCTGAGGGGAGGGGCAGGACAAGGTGGCAGAGGTGGGGCATTTTTACCCAGAGAAGACCAAAAACAGTCAAAGGGATGGTTTAAATACCTGTCTTTTCTATTTTTTCACTGACTCCATGGTAACAGATTTTGCAGTAAGAACCAACATACACACATCCCTAAAACATATTTCATCAAATAATGCTGACATTATGTGTAGCACATGCTGATATTTTCTATTCTATTCTCATTTTCTCTCTCTTTTTTTCAAACAAATGATAATGAAAATCCACTAAGCTAAGGGTCATGACCAACTAACGGGTAACACCCTTCAGTTTGGAAATACTGATTTAAATTTTGGCTTGCTCTAAGCTATAATTCACATTTGACATTTAGATCATATTTTTCCCCTACTGACTCTCAAGTGGGAGCTGAGAGGCAGTACCTAATAAATACAGAGAAAAAGAAAGAAGGAAGGAAAATTGATATTTACAACACAACCAAGTTTTAATGTGAGTTAAAAAAACGGGTTTCTTTTTGATGGAAATCTTCTAAAATTGTGCTGATGGTTACACACTTCTGTGAATGCACCAAAAACCATTGAATTGTACATTTTAAATGAGGTCATTGTTGCTCTGTGAGTTATATGTCAACAAAGCCGTGAAAATCAGCAAACGAGAACAAACAGTTCTGGCCTAGGCCCTGCGAGGCCTCTTTCTAAGGCCGCCGCACGCCCATCTGGGCCATTAAACTCCTCAAGGCGCTCTGACTTCCTTGTGTAAGAGATATTGCTGTTTCTTATAAGGCACTGATGTACTTTTGCTTCCTTATTGGGGTAAAATTTAAAGACCATAACACTGATGGTTTTAAGAGTGCAGTTAAAAAAAGTTTTGACAAATGTATAAACACATGTAACAACCGCCCCAATCAATACACAGCACATTTCCACGGCCCCAGCAGGTTTCCTTGGTCCCCTTCCCAGCAAATCC\t*\tSA:Z:chr18,77371711,+,1447M50D184M980S,60,88,1375;\tMD:Z:16T6C7G4T0G4C2T0T22A5T10T12A19G5C1C9A4C2T0T1C26C11A28C6A12C0A5G18A5G6C10C5C6A70G4T10T0G4T1G4G3T200G738G52\tRG:Z:GATKSVContigAlignments\tNM:i:93\tAS:i:1115\tXS:i:0", true);
        data.add(new Object[]{new AlignedContig.GoodAndBadMappings(Arrays.asList(one, two)),
                false,
                new AlignedContig.GoodAndBadMappings(Arrays.asList(new AlignmentInterval(new SimpleInterval("chr18:77371711-77373157"), 1, 1447, TextCigarCodec.decode("1447M1164S"), true, 60, AlignmentInterval.NO_NM, AlignmentInterval.NO_AS, ContigAlignmentsModifier.AlnModType.FROM_SPLIT_GAPPED_ALIGNMENT),
                                                     new AlignmentInterval(new SimpleInterval("chr18:77373176-77373513"), 1166, 1503, TextCigarCodec.decode("1165H338M1108S"), true, 60, AlignmentInterval.NO_NM, AlignmentInterval.NO_AS, ContigAlignmentsModifier.AlnModType.FROM_SPLIT_GAPPED_ALIGNMENT),
                                                     new AlignmentInterval(new SimpleInterval("chr18:77373208-77373391"), 1448, 1631, TextCigarCodec.decode("1447S184M980S"), true, 60, AlignmentInterval.NO_NM, AlignmentInterval.NO_AS, ContigAlignmentsModifier.AlnModType.FROM_SPLIT_GAPPED_ALIGNMENT),
                                                     new AlignmentInterval(new SimpleInterval("chr18:77373514-77374571"), 1554, 2611, TextCigarCodec.decode("1165H388S1058M"), true, 60, AlignmentInterval.NO_NM, AlignmentInterval.NO_AS, ContigAlignmentsModifier.AlnModType.FROM_SPLIT_GAPPED_ALIGNMENT)))
        });

        return data.toArray(new Object[data.size()][]);
    }
    @Test(groups = "sv", dataProvider = "forSortingAfterGapSplit")
    public void testSortingAfterGapSplit( final AlignedContig.GoodAndBadMappings input, final boolean keepSplitChildrenTogether,
                                          final AlignedContig.GoodAndBadMappings expectedOutput) {
        Assert.assertEquals(AlignedContig.splitGaps(input, keepSplitChildrenTogether), expectedOutput);
    }

    @DataProvider Object[][] forGetMaxOverlapPairs() {
        final List<Object[]> data = new ArrayList<>(20);

        final List<AlignmentInterval> alignments = Arrays.asList(new AlignmentInterval(new SimpleInterval("chr18:77371711-77373157"), 1, 1447, TextCigarCodec.decode("1447M1164S"), true, 60, AlignmentInterval.NO_NM, AlignmentInterval.NO_AS, ContigAlignmentsModifier.AlnModType.FROM_SPLIT_GAPPED_ALIGNMENT),
                                                                 new AlignmentInterval(new SimpleInterval("chr18:77373176-77373513"), 1166, 1503, TextCigarCodec.decode("1165H338M1108S"), true, 60, AlignmentInterval.NO_NM, AlignmentInterval.NO_AS, ContigAlignmentsModifier.AlnModType.FROM_SPLIT_GAPPED_ALIGNMENT),
                                                                 new AlignmentInterval(new SimpleInterval("chr18:77373208-77373391"), 1448, 1631, TextCigarCodec.decode("1447S184M980S"), true, 60, AlignmentInterval.NO_NM, AlignmentInterval.NO_AS, ContigAlignmentsModifier.AlnModType.FROM_SPLIT_GAPPED_ALIGNMENT),
                                                                 new AlignmentInterval(new SimpleInterval("chr18:77373514-77374571"), 1554, 2611, TextCigarCodec.decode("1165H388S1058M"), true, 60, AlignmentInterval.NO_NM, AlignmentInterval.NO_AS, ContigAlignmentsModifier.AlnModType.FROM_SPLIT_GAPPED_ALIGNMENT));

        final Map<AlignmentInterval, Tuple2<Integer, Integer>> overlapMap = new HashMap<>(4);
        overlapMap.put(alignments.get(0), new Tuple2<>(-1, 282));
        overlapMap.put(alignments.get(1), new Tuple2<>(282, 56));
        overlapMap.put(alignments.get(2), new Tuple2<>(56, 78));
        overlapMap.put(alignments.get(3), new Tuple2<>(78, -1));
        data.add(new Object[]{alignments, overlapMap});

        return data.toArray(new Object[data.size()][]);
    }
    @Test(groups = "sv", dataProvider = "forGetMaxOverlapPairs")
    public void testGetMaxOverlapPairs(final List<AlignmentInterval> alignments, final Map<AlignmentInterval, Tuple2<Integer, Integer>> expected) {
        final Map<AlignmentInterval, Tuple2<Integer, Integer>> actual = AlignedContig.getMaxOverlapPairs(alignments);
        twoSetsEqualIgnoreOrder(new HashSet<>(actual.keySet()), new HashSet<>(expected.keySet()));
        actual.forEach((k,v) -> Assert.assertEquals(v, expected.get(k)));
    }

    private static void twoSetsEqualIgnoreOrder(final Set<AlignmentInterval> one, final Set<AlignmentInterval> two) {
        Iterator<AlignmentInterval> iterator = one.iterator();
        while (iterator.hasNext()) {
            AlignmentInterval next = iterator.next();
            Assert.assertTrue(two.contains(next));
            two.remove(next);
            iterator.remove();
        }
        Assert.assertTrue(two.isEmpty());
    }
}
